• python3正则表达式


    一、元字符

    元字符(metacharacter),它们并不能匹配自身,它们定义了字符类、子组匹配和模式重复次数等。元字符的完整列表:.   ^   $   *   +   ?   { }   [ ]      |   ( )

    1、方括号 [ ],它们指定一个字符类用于存放你需要匹配的字符集合。可以单独列出需要匹配的字符,也可以通过两个字符和一个横杆 - 指定匹配的范围。例如 [abc] 会匹配字符 ab 或 c[a-c] 可以实现相同的功能。后者使用范围来表示与前者相同的字符集合。如果你想只匹配小写字母,你的 RE 可以写成 [a-z]

    注意:元字符在方括号中不会触发“特殊功能”,在字符类中,它们只匹配自身。例如 [akm$] 会匹配任何字符 'a''k''m' 或 '$''$' 是一个元字符,但在方括号中它不表示特殊含义,它只匹配 '$' 字符本身。

    你还可以匹配方括号中未列出的所有其他字符。做法是在类的开头添加一个脱字符号 ^ ,例如 [^5] 会匹配除了 '5' 之外的任何字符。

    import re
    print(re.search(r'[^5]','56789'))
    <re.Match object; span=(1, 2), match='6'>

    2、反斜杠  ,跟 Python 的字符串规则一样,如果在反斜杠后边紧跟着一个元字符,那么元字符的“特殊功能”也不会被触发。例如你需要匹配符号 [ 或 ,你可以在它们前面加上一个反斜杠,以消除它们的特殊功能:[\。反斜杠后边跟一些字符还可以表示特殊的意义,例如表示十进制数字,表示所有的字母或者表示非空白的字符集合。

    例如:w 匹配任何单词字符。如果正则表达式以字节的形式表示,这相当于字符类 [a-zA-Z0-9_]
    下边列举一些反斜杠加字符构成的特殊含义:

    特殊字符
    含义
    d
    匹配任何十进制数字;相当于类 [0-9]
    D
    与 d 相反,匹配任何非十进制数字的字符;相当于类 [^0-9]
    s
    匹配任何空白字符(包含空格、换行符、制表符等);相当于类 [ fv]
    S
    与 s 相反,匹配任何非空白字符;相当于类 [^ fv]
    w
    匹配任何单词字符,见上方解释
    W
    于 w 相反
    
    匹配单词的开始或结束
    B
    与  相反

     它们可以包含在一个字符类中,并且一样拥有特殊含义。例如 [s,.] 是一个字符类,它将匹配任何空白字符(s 的特殊含义),',' 或 '.'

    3、点.,它匹配除了换行符以外的任何字符。如果设置了 re.DOTALL 标志,. 将匹配包括换行符在内的任何字符。

    4、星*重复,当然它不是匹配 '*' 字符本身(我们说过元字符都是有特殊能力的),它用于指定前一个字符匹配零次或者多次。

    例如 ca*t 将匹配 ct(0 个字符 a),cat(1 个字符 a),caaat(3 个字符 a),等等。需要注意的是,由于受到 C 语言的 int 类型大小的内部限制,正则表达式引擎会限制字符 'a' 的重复个数不超过 20 亿个

    import re
    print(re.search(r'ca*t','caaaatttt'))
    <re.Match object; span=(0, 6), match='caaaat'>

    正则表达式默认的重复规则是贪婪的,当你重复匹配一个 RE 时,匹配引擎会尝试尽可能多的去匹配。直到 RE 不匹配或者到了结尾,匹配引擎就会回退一个字符,然后再继续尝试匹配。
    我们通过例子一步步的给大家讲解什么叫“贪婪”:先考虑一下表达式 a[bcd]*b,首先需要匹配字符 'a',然后是零个到多个 [bcd],最后以 'b' 结尾。那现在想象一下,这个 RE 匹配字符串 abcbd 会怎样?

    步骤 匹配 说明
    1 a 匹配 RE 的第一个字符 'a'
    2 abcbd 引擎在符合规则的情况下尽可能地匹配 [bcd]*,直到该字符串的结尾
    3 失败 引擎尝试匹配 RE 最后一个字符 'b',但当前位置已经是字符串的结尾,所以失败告终
    4 abcb 回退,所以 [bcd]* 匹配少一个字符
    5 失败 再一次尝试匹配 RE 最后一个字符 'b',但字符串最后一个字符是 'd',所以失败告终
    6 abc 再次回退,所以 [bcd]* 这次只匹配 'bc'
    7 abcb 再一次尝试匹配字符 'b',这一次字符串当前位置指向的字符正好是 'b',匹配成功

    5、+重复,用于指定前一个字符匹配一次或者多次。

    注意 * 和 + 的区别:* 匹配的是零次或者多次,所以被重复的内容可能压根儿不会出现;+ 至少需要出现一次。例如 ca+t 会匹配 cat 和 caaat,但不会匹配 ct

    6、问号 ?重复,用于指定前一个字符匹配零次或者一次。你可以这么想,它的作用就是把某种东西标志位可选的。例如 小?黄人 可以匹配 小黄人,也可以匹配 黄人

    import re
    print(re.search(r'小?黄人','小黄人'))
    print(re.search(r'小?黄人','黄人'))
    print(re.search(r'小?黄人','小小小黄人'))
    <re.Match object; span=(0, 3), match='小黄人'>
    <re.Match object; span=(0, 2), match='黄人'>
    <re.Match object; span=(2, 5), match='小黄人'>

    7、 {m,n}(m 和 n 都是十进制整数),上边讲到的几个元字符都可以使用它来表达,它的含义是前一个字符必须匹配 m 次到 n 次之间。例如 a/{1,3}b 会匹配 a/ba//b 和 a///b。但不会匹配 ab(没有斜杠);也不会匹配 a////b(斜杠超过三个)。
    你可以省略 m 或者 n,这样的话,引擎会假定一个合理的值代替。省略 m,将被解释为下限 0;省略 n 则会被解释为无穷大(事实上是上边我们提到的 20 亿)。

    如果是 {,n} 相当于 {0,n};如果是 {m,} 相当于 {m,+无穷};如果是 {n},则是重复前一个字符 n 次。另外还有一个超容易出错的是写成 {m, n},看着挺美,但注意,正则表达式里边不能随意添加空格,不然会改变原来的含义。

    其实 *+ 和 ? 都可以使用 {m,n} 来代替。{0,} 跟 * 是一样的;{1,} 跟 + 是一样的;{0,1} 跟 ? 是一样的。不过还是鼓励大家记住并使用 *+ 和 ?,因为这些字符更短并且更容易阅读。

    还有一个原因是匹配引擎对 * + ? 做了优化,效率要更高些。

    二、使用正则表达式

    Python 通过 re 模块为正则表达式引擎提供一个接口,同时允许你将正则表达式编译成模式对象,并用它们来进行匹配。

    re 模块是使用 C 语言编写,所以效率比你用普通的字符串方法要高得多;将正则表达式进行编译(compile)也是为了进一步提高效率;后边我们会经常提到“模式”,指的就是正则表达式被编译成的模式对象。

    1、实现匹配

    当你将正则表达式编译之后,你就得到一个模式对象。那你拿他可以用来做什么呢?模式对象拥有很多方法和属性,我们下边列举最重要的几个来讲:

    方法 功能
    match() 判断一个正则表达式是否从开始处匹配一个字符串
    search() 遍历字符串,找到正则表达式匹配的第一个位置
    findall() 遍历字符串,找到正则表达式匹配的所有位置,并以列表的形式返回
    finditer() 遍历字符串,找到正则表达式匹配的所有位置,并以迭代器的形式返回

    如果没有找到任何匹配的话,match() 和 search() 会返回 None;如果匹配成功,则会返回一个匹配对象(match object),包含所有匹配的信息:例如从哪儿开始,到哪儿结束,匹配的子字符串等等。

    接下来我们一步步讲解:

    >>> import re
    >>> p = re.compile('[a-z]+')
    >>> p
    re.compile('[a-z]+')

    现在,你可以尝试使用正则表达式 [a-z]+ 去匹配各种字符串。

    例如:

    >>> p.match("")
    >>> print(p.match(""))
    None

    因为 + 表示匹配一次或者多次,所以空字符串不能被匹配。因此,match() 返回 None。

    我们再尝试一个可以匹配的字符串:

    >>> m = p.match('xiaohuangren')
    >>> m
    <re.Match object; span=(0, 12), match='xiaohuangren'>

    在这个例子中,match() 返回一个匹配对象,我们将其存放在变量 m 中,以便日后使用。

    接下来让我们来看看匹配对象里边有哪些信息吧。匹配对象包含了很多方法和属性,以下几个是最重要的:

    方法 功能
    group() 返回匹配的字符串
    start() 返回匹配的开始位置
    end() 返回匹配的结束位置
    span() 返回一个元组表示匹配位置(开始,结束)
    >>> m.group()
    'xiaohuangren'
    >>> m.start()
    0
    >>> m.end()
    12
    >>> m.span()
    (0, 12)

    由于 match() 只检查正则表达式是否在字符串的起始位置匹配,所以 start() 总是返回 0。
    然而,search() 方法可就不一样咯:

    >>> print(p.match('^-^xiaohuangren'))
    None
    >>> m = p.search('^-^xiaohuangren')
    >>> print(m)
    <re.Match object; span=(3, 15), match='xiaohuangren'>
    >>> m.group()
    'xiaohuangren'
    >>> m.span()
    (3, 15)

    在实际应用中,最常用的方式是将匹配对象存放在一个局部变量中,并检查其返回值是否为 None。
    形式通常如下:

    p = re.compile( ... )
    m = p.match( 'string goes here' )
    if m:
        print('Match found: ', m.group())
    else:
        print('No match')

    有两个方法可以返回所有的匹配结果,一个是 findall(),另一个是 finditer()。

    findall() 返回的是一个列表:

    >>> p = re.compile('d+')
    >>> p.findall('3只小黄人,10条腿,其他4条腿从哪里来的?')
    ['3', '10', '4']

    findall() 需要在返回前先创建一个列表,而 finditer() 则是将匹配对象作为一个迭代器返回:

    >>> iterator = p.finditer('3只小黄人,10条腿,其他4条腿从哪里来的?')
    >>> iterator
    <callable_iterator object at 0x0000000002FC0B38>
    >>> for match in iterator:
        print(match.span())    
    (0, 1)
    (6, 8)
    (13, 14)

    如果列表很大,那么返回迭代器的效率要高很多。

    2、模块级别的函数

    使用正则表达式也并非一定要创建模式对象,然后调用它的匹配方法。因为,re 模块同时还提供了一些全局函数,例如 match(),search(),findall(),sub() 等等。这些函数的第一个参数是正则表达式字符串,其他参数跟模式对象同名的方法采用一样的参数;返回值也一样,同样是返回 None 或者匹配对象。

    >>> print(re.match(r'Froms+','From_xiaohuangren,com'))
    None
    >>> re.match(r'Froms+','From xiaohuangren,com')
    <re.Match object; span=(0, 5), match='From '>

    其实,这些函数只是帮你自动创建一个模式对象,并调用相关的函数(上一篇的内容,还记得吗?)。它们还将编译好的模式对象存放在缓存中,以便将来可以快速地直接调用。
    那我们到底是应该直接使用这些模块级别的函数呢,还是先编译一个模式对象,再调用模式对象的方法呢?这其实取决于正则表达式的使用频率,如果说我们这个程序只是偶尔使用到正则表达式,那么全局函数是比较方便的;如果我们的程序是大量的使用正则表达式(例如在一个循环中使用),那么建议你使用后一种方法,因为预编译的话可以节省一些函数调用。但如果是在循环外部,由于得益于内部缓存机制,两者效率相差无几。

    3、编译标志

     编译标志让你可以修改正则表达式的工作方式。在 re 模块下,编译标志均有两个名字:完整名和简写,例如 IGNORECASE 简写是 I(如果你是 Perl 的粉丝,那么你有福了,因为这些简写跟 Perl 是一样的,例如 re.VERBOSE 的简写是 re.X)。另外,多个标志还可以同时使用(通过“|”),如:re.I | re.M 就是同时设置 I 和 M 标志。

    下边列举一些支持的编译标志:

    标志 含义
    ASCII, A 使得转义符号如 ws 和 d 只能匹配 ASCII 字符
    DOTALL, S 使得 . 匹配任何符号,包括换行符
    IGNORECASE, I 匹配的时候不区分大小写
    LOCALE, L 支持当前的语言(区域)设置
    MULTILINE, M 多行匹配,影响 ^ 和 $
    VERBOSE, X (for 'extended') 启用详细的正则表达式

    下面我们来详细讲解一下它们的含义:

    A
    ASCII
    使得 wWBs 和 只匹配 ASCII 字符,而不匹配完整的 Unicode 字符。这个标志仅对 Unicode 模式有意义,并忽略字节模式。

    S
    DOTALL
    使得 . 可以匹配任何字符,包括换行符。如果不使用这个标志,. 将匹配除了换行符的所有字符。

    I
    IGNORECASE
    字符类和文本字符串在匹配的时候不区分大小写。举个例子,正则表达式 [A-Z] 也将会匹配对应的小写字母,像 FishC 可以匹配 FishCfishc 或 FISHC 等。如果你不设置 LOCALE,则不会考虑语言(区域)设置这方面的大小写问题。

    L
    LOCALE
    使得 wW 和 B 依赖当前的语言(区域)环境,而不是 Unicode 数据库。

    区域设置是 C 语言的一个功能,主要作用是消除不同语言之间的差异。例如你正在处理的是法文文本,你想使用 w+ 来匹配单词,但是 w 只是匹配 [A-Za-z] 中的单词,并不会匹配 'é' 或 '&#231;'。如果你的系统正确的设置了法语区域环境,那么 C 语言的函数就会告诉程序 'é' 或 '&#231;' 也应该被认为是一个字符。当编译正则表达式的时候设置了 LOCALE 的标志,w+ 就可以识别法文了,但速度多少会受到影响。

    M
    MULTILINE
    ^ 和 $ 我们还没有提到,别着急,后边我们有细讲...)
    通常 ^ 只匹配字符串的开头,而 $ 则匹配字符串的结尾。当这个标志被设置的时候,^ 不仅匹配字符串的开头,还匹配每一行的行首;& 不仅匹配字符串的结尾,还匹配每一行的行尾。

    X
    VERBOSE
    这个标志使你的正则表达式可以写得更好看和更有条理,因为使用了这个标志,空格会被忽略(除了出现在字符类中和使用反斜杠转义的空格);这个标志同时允许你在正则表达式字符串中使用注释,# 符号后边的内容是注释,不会递交给匹配引擎(除了出现在字符类中和使用反斜杠转义的 #)。

    下边是使用 re.VERBOSE 的例子,大家看下正则表达式的可读性是不是提高了不少:

    charref = re.compile(r"""
     &[#]                # 开始数字引用
     (
         0[0-7]+         # 八进制格式
       | [0-9]+          # 十进制格式
       | x[0-9a-fA-F]+   # 十六进制格式
     )
     ;                   # 结尾分号
    """, re.VERBOSE)

    如果没有设置 VERBOSE 标志,那么同样的正则表达式会写成:

    charref = re.compile("&#(0[0-7]+|[0-9]+|x[0-9a-fA-F]+);")

    三、更多强大的功能

    1、更多元字符

    还有一些元字符我们没有讲到,接下来我们继续

    有些元字符它们不匹配任何字符,只是简单地表示成功或失败,因此这些字符也称之为零宽断言。例如  表示当前位置位于一个单词的边界,但  并不能改变位置。因此,零宽断言不应该被重复使用,因为  并不会修改当前位置,所以  跟  是没什么两样的。

    很多人可能不理解“改变位置”和“零宽断言”的意思?我尝试解释下,比如 abc 匹配完 a 之后,咱的当前位置就会移动,才能继续匹配 b,依次类推...但是 abc 的话, 表示当前位置在单词的边界(单词的第一个字母或者最后一个字母),这时候当前位置不会发生改变,接着将 a 与当前位置的字符进行匹配......

    |
    或操作符,对两个正则表达式进行或操作。如果 A 和 B 是正则表达式,A | B 会匹配 A 或 B 中出现的任何字符。为了能够更加合理的工作,| 的优先级非常低。例如 Fish|C 应该匹配 Fish 或 C,而不是匹配 Fis,然后一个 'h' 或 'C'。
    同样,我们使用 | 来匹配 '|' 字符本身;或者包含在一个字符类中,像这样 [|]。

    ^
    匹配字符串的起始位置。如果设置了 MULTILINE 标志,就会变成匹配每一行的起始位置。在 MULTILINE 中,每当遇到换行符就会立刻进行匹配。
    举个例子,如果你只希望匹配位于字符串开头的单词 From,那么你的正则表达式可以写为 ^From:

    >>> print(re.search('^From', 'From Here to Eternity'))
    <re.Match object; span=(0, 4), match='From'>
    >>> print(re.search('^From', 'Reciting From Memory'))
    None

    $
    匹配字符串的结束位置,每当遇到换行符也会离开进行匹配

    >>> print(re.search('}$', '{block}'))
    <re.Match object; span=(6, 7), match='}'>
    >>> print(re.search('}$', '{block} '))
    None
    >>> print(re.search('}$', '{block}
    '))
    <re.Match object; span=(6, 7), match='}'>

    同样,我们使用 $ 来匹配 '$' 字符本身;或者包含在一个字符类中,像这样 [$]

    A
    只匹配字符串的起始位置。如果没有设置 MULTILINE 标志的时候,A 和 ^ 的功能是一样的;但如果设置了 MULTILINE 标志,则会有一些不同:A 还是匹配字符串的起始位置,但 ^ 会对字符串中的每一行都进行匹配。

    
    只匹配字符串的结束位置。

    
    单词边界,这是一个只匹配单词的开始和结尾的零宽断言。“单词”定义为一个字母数字的序列,所以单词的结束指的是空格或者非字母数字的字符。
    下边例子中,class 只有在出现一个完整的单词 class 时才匹配;如果出现在别的单词中,并不会匹配。

    >>> p = re.compile(r'class')
    >>> print(p.search('no class at all'))
    <re.Match object; span=(3, 8), match='class'>
    >>> print(p.search('the declassified alroritem'))
    None
    >>> print(p.search('one subclass is'))
    None

    在使用这些特殊的序列的时候,有两点是需要注意的:第一点需要注意的是,Python 的字符串跟正则表达式在有些字符上是有冲突的(回忆之前反斜杠的例子)。比如说在 Python 中, 表示的是退格符(ASCII 码值是 8)。所以,你如果不使用原始字符串,Python 会将  转换成退格符处理,这样就肯定跟你的预期不一样了。

    下边的例子中,我们故意不写表示原始字符串的 'r',结果确实大相庭径:

    >>> p = re.compile('class')
    >>> print(p.search('no class at all'))
    None
    >>> print(p.search('' + 'class' + ''))  
    <_sre.SRE_Match object; span=(0, 7), match='x08classx08'>

    第二点需要注意的是,在字符类中不能使用这个断言。跟 Python 一样,在字符类中, 只是用来表示退格符。

    B
    另一个零宽断言,与  的含义相反,B 表示非单词边界的位置。

    分组
    通常在实际的应用过程中,我们除了需要知道一个正则表达式是否匹配之外,还需要更多的信息。对于比较复杂的内容,正则表达式通常使用分组的方式分别对不同内容进行匹配。
    下边的例子,我们将 RFC-822 头用“:”号分成名字和值分别匹配:

  • 相关阅读:
    线程池
    并发工具类
    并发编程专题
    HashMap底层源码剖析
    ConcurrentHashMap底层实现
    双列集合Map相关面试题
    Map与HashMap
    使用IDEA搭建一个 Spring + Spring MVC 的Web项目(零配置文件)
    一个oracle的实例(包含传参数)
    一个oracle的实例(包含定位错误)
  • 原文地址:https://www.cnblogs.com/qinguodong/p/10948085.html
Copyright © 2020-2023  润新知