美文网首页
python 正则大法好

python 正则大法好

作者: 天空蓝雨 | 来源:发表于2020-05-19 19:58 被阅读0次

    PythoN 正则 官方介绍:
    https://docs.python.org/3.2/library/re.html
    https://docs.python.org/zh-cn/3.8/library/re.html

    • 怎么在 有 多个 分组的情况下,拿到整个的匹配表达式(以这个功能切入)

    最近使用分组功能,有点问题,还是源于对正则的不熟悉
    例如下面的:

    (https?|ftp|file)://(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})  
    

    我想匹配一个硬编码的东西,像上面这样,我想 匹配https 或者 ftp file 之一的,那我就的 用 | 匹配左边或者右边。如果不止一个 左边或者右边,那就需要几个 | 一起组合,那我就得加个 (|) xxx(|),但是问题是,加了 () 他就看做是一个分组,使用 match 和 search 都还好,可以使用 group(), 拿到 整个匹配的东东西,但是万万一使用findall 就不行了,findall 会把只会把你的分组变成一个元祖(一个分组,就直接是那个分组的字符串,多个分组就是每个分组组成的元祖,没有分组,就是整个的匹配 下来的字符串),只有不用分组regx 里面不用 (),findall 才可以拿到,整个字串。

    其实 re 给我们考虑到了 这个问题。看下面的操作把

    先来看看 match search和 findall 对于分组的处理都是什么样的把

    match
    print(re.match(r"(aa.*?aaa)", "aafgdfaaa").groups())
    >>('aafgdfaaa',)
    print(re.match(r"(aa.*?aaa)", "aafgdfaaa").group())
    >> "aafgdfaaa"
    print(re.match(r"aa.*?aaa", "aafgdfaaa").group())
    >>"aafgdfaaa"
    print(re.match(r"((aa).*?(aa))(a)", "aafgdfaaa").groups())
    >> ('aafgdfaa', 'aa', 'aa', 'a')
    re.match(r"(((aa).*?(aa))(a))", "aafgdfaaa").groups())
    >>('aafgdfaaa', 'aafgdfaa', 'aa', 'aa', 'a')
    
    search
    print(re.search(r"(((aa).*?(aa))(a))", "aafgdfaaa").groups())
    >>('aafgdfaaa', 'aafgdfaa', 'aa', 'aa', 'a')
    

    search 和 match 都差不多,无非是 match 默认加了个 ^ 开头匹配。
    使用 group(index=0) 可以取到 整个匹配表达式(不管你最外面有木有 ( ) 有的话, group(0) == group(1)== groups()[0]) 和 其他分组排列下来
    ps: 不加 ( ) groups() => () 为空,但是 group(0) 却可以取出整个匹配的表达式

    findall
    print(re.findall(r"(aa).*?(aa)", "aafgdfaaa"))
    >>[('aa', 'aa')]
    print(re.findall(r"((aa).*?(aa))", "aafgdfaaa"))
    >>[('aafgdfaa', 'aa', 'aa')]
    print(re.findall(r"((aa).*?(aa))(a)", "aafgdfaaa"))
    >>[('aafgdfaa', 'aa', 'aa', 'a')]
    print(re.findall(r"(((aa).*?(aa))(a))", "aafgdfaaa"))
    >>[('aafgdfaaa', 'aafgdfaa', 'aa', 'aa', 'a')]
    

    可以看到,findall 其实是把 () 当做分组, 遵守从外到内,从前到后的原则,所以有多个() 嵌套的,findall 列表的元素就有几个,类似于 一个图形里面有几个三角形,顺序是外面的优先,然后是他的子级,同一层级,从前到后排列。(深度优先)(其实match 和search 的 group groups都是这种逻辑)
    findall 只是返回一个 groups (有分组,优先) / group(0) ( 无分组) 的 元素的列表(这里就和group 没关系了)。
    所以第一种方法: 可以findall 匹配整个表达式的是 可以 使用一个最大的 () 然后取到 结果列表每个元素的第一个值(每个元祖的 第一个就是最大的那个 () 的分组)

    第二种方法就是利用 正则里面的
    关于 ?可能打部分人的第一印象是 非贪心, 例如:
    举两个官方文档的例子:
    1 、Causes the resulting RE to match 0 or 1 repetitions of the preceding RE. ab? will match either ‘a’ or ‘ab’
    2、{m,n}?
    the 6-character string 'aaaaaa', a{3,5} will match aaaaa characters, while a{3,5}? will only match aaa characters.
    所以 ? 第一作用是限制匹配的长度,拿到最短的,而且符合条件的字串
    但是 ? 还有 另外一个作用 :
    (?…) ... 是任意的正则表达式, 这种扩展通常并不创建新的组合; (?P<name>...) 是唯一的例外
    ? 后面的一个字符,代表了 这个"分组"的处理规则。
    比如:
    (?:…) ? 后面加个: 就表示非捕获组,但该分组所匹配的子字符串 不能 在执行匹配后被获取或是之后在模式中被引用
    例如:

    text2 = '''/* this is a
    ... multiline comment */
    >>> comment = re.compile(r'/\*((?:.|\n)*?)\*/')  # 这里使用?: 来穿件一个非捕
    获组。匹配. 或者 \n ( 因为 . 不能匹配  \n) 所以findall 元素只是一个 最外面的分组。 
    >>> comment.findall(text2)
    [' this is a\n multiline comment ']
    
    • re 里面的 |

    这个符号再解释一下吧,就是 匹配左边或者 右边的

    A | B(其中A和B可以是任意RE)会创建一个匹配A或B的正则表达式。任意数量的RE都可以由 '|'分隔 通过这种方式。也可以在组内部使用。 扫描目标字符串时,RE用 “ |”分隔 从左到右尝试。当一个模式完全匹配时,该分支被接受。这意味着一旦一个比赛,乙将不会进一步测试,即使它会产生一个较长的整体搭配。换句话说, “ |” 操作员从不贪婪。匹配文字 “ |” ,使用 \ | ,或将其包含在字符类中,如 [|]所示

    单个字符请使用[xx]

    idea 智能提示,如果只是一个字符,使用[ xx ] 比较快哦

    re.match(r"aa(?:fg|g).*?aaa", "aafgdfaaa")
    

    两个字符就不会有提示了,哈哈

    下面附上我的re 官方文档阅读笔记:

    \color{blue}{正则表达式使用反斜杠字符 ('\') 取消特殊符号(. * 等) 或者 特殊形式('' "" 等的转义}

    普通字符串 比如:\color{green}{. * \ 等} 都是着正则特殊含义的字符串

    要匹配一个反斜杠字面值,用户可能必须写成 "\\\\" \color{blue}{来作为模式字符串}
    因为\color{green}{正则表达式必须为} "\\",而每个反斜杠在普通 Python 字符串字面值中又必须表示为 r"\\"。
    \color{red}{字符串字面值中使用的反斜杠如果有任何无效的转义序列 ,会报 SyntaxError } 以前我就遇到过,这个问题?

    比如匹配 ' 就要用 \' 这种特殊形式 或者 * 使用 \* 来分别匹配 ' 或者 * 它本身
    但是有时候 \ 匹配他自己,就需要 \\ ,有时候写起来比较麻烦,这时候,需要使用 r""。
    在带有 'r' 前缀的字符串字面值中,反斜杠不必做任何特殊处理。 r"\n" 表示包含 '\' 和 'n'。

    import re
    # re.search("\[\]\\", 'ffggfh[]\\')  
    # 这时候,search 第一个参数 不加 r,就会报错 因为  \\ 字面为 一个 \ ,
    # 等匹配的时候。就会发现,这个字符串右边是 ",不是一个合格的字符串了。
    re.search(r"\[\]\\ ", 'ffggfh[]\ ')  # 这样写就不会报错了,因为即使不加 r 第一个参数匹配的时候,已经是一个 \ ,
                                        # 但是他的后面是个 空格, 这样最多是匹配不到
                                        # 而第二个参数,是默认要匹配的字符串,如果单独一个 \ ,他会变成  \\ 传到下一级进行匹配。
                                        # 无非是 py 字面字符串的一个完善,但是这种做法,会迷惑初学者
                
    
    <re.Match object; span=(6, 10), match='[]\\ '>
    
    'ffggfh[]\\'
    # 'ffggfh[]\\'
    r"ffggfh[]\\"
    # 'ffggfh[]\\\\'
    
    
    'ffggfh[]\\\\'
    

    \color{blue}{正则表达式语法}

    正则可以拼接

    如果 A 和 B 都是正则表达式, 那么 AB 也是正则表达式

    正则有普通字符,和特殊字符

    普通字符,比如: abcds, f121, 等
    特殊字符吧比如 | . * ? ( { ? 等,他们都会影响整个表达式的匹配规则。 匹配他们本身请用 \ 转义他们
    \color{blue}{应用一个内层重复嵌套,可以使用括号 ()}
    比如:(?:a{6})* 匹配6个 'a' 字符重复任意次数, 同时这里使用了 ?: 这就把当前的() 变为不产生分组,而仅仅是 为了表达式本身运算的优先级了。

    特殊字符的每个含义

    1、\color{green}{.} 匹配除换行符 \n 的任意字符,如果要匹配所有字符,请使用 标志 re.S 或者 re.DOTALL

    2、^ 匹配开头,MULTILINE 模式也匹配换行后的首个符号 (MULTILINE意思是多行,每一行首都可以加 ^ ,来表示)

    re.match(r"^xxxa\n^bb", "xxxa\nbb", re.MULTILINE).group()   # 第一参数中的第二个^ 其实写不写都行,因为他仅仅是表示 下一行的行首。
    
    'xxxa\nbb'
    

    3、\color{green}{}匹配行末 MULTILINE 模式依然匹配换行符前 ( 默认到最后一个结尾,不管是不是 \n ,但是MULTILINE 把每个换行符都看做一个结尾了)
    区别看下面的例子

    print(re.findall(r"foo.$", "foo1\nfoo2\n"))
    re.findall(r"foo.$", "foo1\nfoo2\n", re.MULTILINE)
    
    ['foo2']
    
    
    
    
    
    ['foo1', 'foo2']
    

    4、\color{green}{*} 匹配0 或 多次, 尽可能多匹配,所以 贪吃
    ab* 会匹配 'a', 'ab'
    不解释,很简单

    5、\color{green}{ + } 匹配 至少 1 次,也是贪吃

    re.match(r"ab+","abbbb")
    
    <re.Match object; span=(0, 5), match='abbbb'>
    

    5、\color{green}{?} 匹配最多 1 次,非贪吃,最多一次怕噎死啊
    当然 ? 还有跟多有意思的用法,下面在看

    6、\color{green}{\{m\}} 匹配前一个字符 m 次,不多不少

    7、\color{green}{\{m, n\}} 匹配 m 到 n 次,也是贪吃啦

    8、 {m,n}? 非贪吃 满足 m n 范围,尽可能少吃
    'aaaaaa', a{3,5} 匹配 5个 'a' ,而 a{3,5}? 只匹配3个 'a'

    9、\ 转义特殊字符
    允许你匹配 '*', '?' 他们本身

    10、\color{green}{[]} 匹配 一个字符的集合
    [amk] 匹配 'a', 'm' 'k'

    字符范围,通过用 '-' 将两个字符连起来。比如 [a-z] 将匹配任何小写ASCII字符

    [0-5][0-9] 将匹配从 00 到 59 的两位数字
    [0-9A-Fa-f] 将匹配任何十六进制数位
    ps : [a-z])或者它的位置在首位或者末尾(如 [-a] 或 [a-] - 就只是表示他自己了
    注意 特殊字符在 []会变成 普通字符了,所以 [(+)] 表示 匹配 '(', '+', '', or ')'。 所以我们可以用 [.]来代替 . 了
    注意: \w 或者 \S 这种在集合内依然是有特殊含义的,不会匹配他们本身的。 和单个字符不同

    使用 ^ 获取,不在 [] 里面的东西, ^ 必须是 [^] 第一个字符,否则他就是普通的字符
    [^5] 将匹配不是 '5'的一个字符

    11 、| 匹配 左边或者 右边
    A|B 匹配 A 或者 B
    任意个正则表达式可以用 '|' 连接。它也可以在组合(见下列)内使用
    想要 自由组合请写在 (?:xx|xx) 这样就不会把 () 变成一个分组了,而仅是一个优先级的运算符了

    12、(...) 分组 可以最长见 group 和groups 取值
    可以在之后用 \number 转义序列进行再次匹配,见下列 \number

    13 、\number 匹配数字代表的组合。
    每个括号是一个组合,组合从1开始编号。比如 (.+) \1 匹配 'the the' 或者 '55 55', 但不会匹配 'thethe' (注意组合后面的空格)

    re.match(r"(.+) \1", "th th").group()
    
    'th th'
    

    14 、(?…) 这是个扩展标记法
    '?' 后面的第一个字符决定了这个构建采用什么样的语法。这种扩展通常并不创建新的组合
    (?P<name>...) 是唯一的例外
    (?aiLmsux) 匹配 ( 'a', 'i', 'L', 'm', 's', 'u', 'x' 中的一个或多个模式) 这里的几个字符都是有特殊含义的好吗。(最开始我还以为是简单的字符呢)(这个只是写在正则开头,对整个表达式生效, 单个模式请直接,用下面的模式写就可以了,这个只是方便多个模式写起来简单点, 不想对整个表达式起作用,请用 (?aiLmsux:xxxx)这杨就只是对 xxxx 起作用了)
    re.A (只匹配ASCII字符)
    re.I (忽略大小写)
    re.L (语言依赖), re.M (多行模式), re.S ( . 匹配全部字符)
    re.U (Unicode匹配)
    re.X (冗长模式)
    下面方式只覆盖组合内匹配,括号外的匹配模式不受影响。
    例如:
    (?a:...) 切换为 只匹配ASCII

    15、(?:…) 正则括号的非捕获版本 (这个比较重要,所以单独写出来)、
    匹配在括号内的任何正则表达式,但该分组所匹配的子字符串 不能 在执行匹配后被获取或是之后在模式中被引用
    这个已经试过很多次了

    16、(?P<name>…) 这个也比较常见 为了复用,匹配的分组 (妈的,这个试了好像只能用作分组的模式)
    这个和 \number 很像 每个组合名只能用一个正则表达式定义,只能定义一次, 没有名字的组合就是 默认\数字 代替那个组合
    (?P<quote>['"]).*?(?P=quote)匹配单引号或者双引号括起来的字符串
    分组名是同一个的,group 取值的时候只算做一个

    print(re.match(r'(?P<quote>[;]).*?(?P=quote)', ";asfdfm;").group(1))
    re.match(r'(?P<quote>[;]).*?(?P=quote)', ";asfdfm;").group()
    
    ;
    
    
    
    
    
    ';asfdfm;'
    

    17、(?=…) 前判断性质的匹配,当xx 后面是 ... 才匹配 xx
    Isaac (?=Asimov) 匹配 'Isaac ' 只有在后面是 'Asimov' 的时候

    18、(?!…) 17 取反, 当xx 后面不是 ... 才匹配 xx

    19、(?<=…) 后判断性质匹配 xx的前面匹配 … 的内容, 才 取到 xx
    (记法 < 嘴对准右边,说明是右边是要吃到的匹配字符,所以在后面是我们需要的东西,左边是判断依据)
    (?<=abc)def 会在 'abcdef' 中找到一个匹配
    注意:
    匹配样式...必须是定长的
    abc 或 a|b 正确
    a* 和 a{3,4} 不正确

    print(re.search('(?<=abc)def', 'abcdef'))
    re.search(r'(?<=-)\w+', 'spam-egg') # 匹配 -后的一个单词
    
    
    <re.Match object; span=(3, 6), match='def'>
    
    
    
    
    
    <re.Match object; span=(5, 8), match='egg'>
    

    20、(?<!…) 19 取反

    21、(?(id/name)yes-pattern|no-pattern)
    这个比较绕,总结来说根据是不是已经匹配了某个组来匹配 yes-pattern
    或者不匹配指定分组时匹配 no-pattern

    yes-pattern : 如果有匹配到的group-id/group-name 就匹配 yes-pattern
    no-pattern : 如果没有匹配到 group-id/group-name 就匹配 no-pattern (可以不写no-pattern, 就默认 "空" 喽)

    不匹配:(我在 group 后加了 ?,这样为了更好的演示)

    import re 
    a = r"this is a test  end"
    b = r"(?P<first_group><)?this is a (?(first_group)test|test  end)"
    re.search(b, a).group()
    >> 'this is a test  end'
    

    匹配:

    import re 
    a = r"<this is a test  end"
    b = r"(?P<first_group><)?this is a (?(first_group)test|test  end)"
    re.search(b, a).group()
    >> '<this is a test'
    

    这个感觉用的蛮多的喽

    一下是一些简单的,那就简单说一下喽

    21、\d 相当于 [0-9]

    22、\D \d 取非

    23、\s [ \t\n\r\f\v]

    24、\S \s 取非

    25、\w 数字字母下划线,中文等

    26、\W \w 取反

    一下是一些简单的标志位,上面已经差不多都讲过了

    回顾一下

    re.A  只匹配 ASCII
    re.S  让. 配 \n
    re.I  忽略大小写
    re.M 多行模式 主要是 ^ 和 $
    

    相关文章

      网友评论

          本文标题:python 正则大法好

          本文链接:https://www.haomeiwen.com/subject/cieaghtx.html