精华内容
下载资源
问答
  • Python正则表达式详解

    2020-12-22 06:08:30
    正则表达式并非Python独有,许多编程语言如Java均有对其的实现。Python中主要由re模块实现正则表达式的使用。 2、工具介绍   正则表达式说到底就是一款工具,一款用于匹配字符串的工具。正则表达式内容繁琐,不...
  • python正则表达式详解

    2019-11-25 22:36:56
    python正则表达式详解 说明: 关于内容:每个知识点尽量用简明方式表述,同时用示例说明,方便以后查阅,方便理解 关于访问:为避免浪费某些同学的时间,先列出所有的内容提要,如果符合要求,则请继续阅读,如果不...

    python正则表达式详解
    说明:
    关于内容:每个知识点尽量用简明方式表述,同时用示例说明,方便以后查阅,方便理解

    关于访问:为避免浪费某些同学的时间,先列出所有的内容提要,如果符合要求,则请继续阅读,如果不符合要求,则可以离开。

    关于权限:   仅用于学习与分享,转载主注明出处。

    内容提要:
    一、正则表达式
    1.1 对正则表达式的理解及python中正则使用的基本方法

    1.1.1对正则表达式的理解

    1.1.2python中正则表达式的基本使用方法

    1.2 python正则表达式中的元字符

    1.2.1-13:".","^","$","*","+","?","*?,+?,??","{m}","{m,n}","{m,n}?","\\","[]","|"

    1.3 python正则中的分组
    1.3.1-10:(...),(?aiLmsux),(?:...),(?P<name>...),(?P=name),(?#...),(?=...),(?!...),(?<=...),(?<!...),

    (?(id/name)yes|no)

    --------------------------------------------------------------------------以下为正文--------------------------------------------------------------

    一、正则表达式
    1.1 对正则表达式的理解及python中正则使用的基本方法
    1.1.1对正则表达式的理解

    正则表达式由普通文本字符和特殊字符(元字符)两种字符组成的一系列的字符串的模式,。元字符在正则表达式中具有特殊意义,它让正则表达式具有更丰富的表达能力。例如,正则表达式 r"a.d"中 ,字符 ‘a’ 和 ‘d’ 是普通字符,’.’ 是元字符,. 可以指代任意字符,它能匹配 ‘a1d’、’a2d’、’acd’等。

    1.1.2python中正则表达式的基本使用方法

    在Python中,利用re模块处理正则表达式,如下代码:

    import re
     
    #字符串1
    regx_string='aab'
     
    #字符串2
    regx_string2='anb'
     
    #生成一个匹配的正则表达式对象
    pattern=re.compile('a.b')
     
    #匹配字符串1
    m1=pattern.match(regx_string)
     
    print(m1)
    # <_sre.SRE_Match object; span=(0, 3), match='aab'>
     
    #匹配字符串2
    m2=pattern.match(regx_string2)
     
    print(m2)
    # <_sre.SRE_Match object; span=(0, 3), match='anb'>
     
    #字符串3
    regx_string3='and'
     
    m3=pattern.match(regx_string3)
     
    print(m3)
    # None
    说明:match方法在字符串与正则表达式匹配的时候,返回的是一个Match对象,如果不匹配,则返回的是None

      python中另一种使用正则的方法:

    # match(pattern, string, flags=0)
    m4=re.match('a.b',regx_string)
     
    print(m4)
    # <_sre.SRE_Match object; span=(0, 3), match='aab'>
    1.2 python正则表达式中的元字符

    1.2.1:"."

     "."      匹配除换行符之外的任何字符:字母、数字、符号、空白字符.

    #示例:
    print(re.match('.a.c.','*a1c '))
    # <_sre.SRE_Match object; span=(0, 5), match='*a1c '>
     
    print(re.match('.a.c.','*a1c\n'))
    # None
    1.2.2:"^"

    "^"      表示匹配字符串的开头

    #示例:
    #匹配一个以ab开关的字符串
    print(re.match('^ab.*','abccd$#2'))
    # <_sre.SRE_Match object; span=(0, 8), match='abccd$#2'>
     
    #匹配一个以ac开关的字符串
    print(re.match('^ac.*','abccd$#2'))
    # None
    1.2.3:"$"

    "$"  匹配字符串的末尾或字符串末尾的换行符之前

    #示例:
    #匹配一个ac结尾的字符串:
    print(re.match('.*ac$','adsfasdfac'))
    # <_sre.SRE_Match object; span=(0, 10), match='adsfasdfac'>
     
    #看结束点
    print(re.match('.*ac$','adsfacdfac\n'))
    # <_sre.SRE_Match object; span=(0, 10), match='adsfacdfac'>
     
    print(re.match('.*ac$','adsfac\ndfac'))
    # None
    说明:$是一般情况下匹配一行的末尾,如果是多行,则没法进行匹配。但可以进行专门指定匹配多行

    print(re.match('.*ac$','adsfac\ndfac',re.M))
    # <_sre.SRE_Match object; span=(0, 6), match='adsfac'>,这种情况相当于没有指定多行的情况下直接匹配'adsfac'
    1.2.4 "*"

     "*"      重复匹配零次或者更多次(贪婪模式).
    贪婪模式:尽可能多的匹配遇到的重复。

    #示例如下:
    #匹配一个以a开始,以b结束的字符串
    print(re.match('a.*b','aaaadbdgdbddf546b'))
    #<_sre.SRE_Match object; span=(0, 17), match='aaaadbdgdbddf546b'>
     
    #限制贪婪模式
    print(re.match('a.*?b','aaaadbdgdbddf546b'))
    #<_sre.SRE_Match object; span=(0, 6), match='aaaadb'>
    1.2.5 "+"

    "+"  重复匹配1次或者更多次(贪婪模式)

    示例:分别用*和+匹配一个以含有'ab'的字符串
    #匹配字符串:aaabcdbfdd
     
    print(re.match('^.*(ab)*.*$','aaabcdbfdd'))
    # <_sre.SRE_Match object; span=(0, 10), match='aaabcdbfdd'>
     
    print(re.match('^.*(ab)+.*$','aaabcdbfdd'))
    # <_sre.SRE_Match object; span=(0, 9), match='aaadcdfbb'>
     
    #匹配字符串:aaadcdfcc
     
    print(re.match('^.*(ab)*.*$','aaadcdfcc'))
    # <_sre.SRE_Match object; span=(0, 9), match='aaadcdfcc'>
     
    print(re.match('^.*(ab)+.*$','aaadcdfbb'))
    # None
    1.2.6 "?"

    "?"      重复匹配0次或者1次(贪婪模式).

    #示例:分别用aaabcdbfdd和aaadcdfbb匹配正则表达式'^.*ab?.*$'
     
    print(re.match('^.*(ab)?.*$','aaabcdbfdd'))
    # <_sre.SRE_Match object; span=(0, 10), match='aaabcdbfdd'>
     
    print(re.match('^.*(ab)?.*$','aaadcdfbb'))
    # <_sre.SRE_Match object; span=(0, 9), match='aaadcdfbb'>
    1.2.7 "*?,+?,??"

    *?,+?,??    *,+,?的非贪婪模式.

    # 示例:正则表达式:'(ab)*','(ab)*?','(ab)+','(ab)+?','(ab)?','(ab)??',匹配字符串:ababababababababab
    #说明:Match对象的group方法在参数为零的情况下,返回的是整个匹配的字符串
    print(re.match('(ab)*','ababababababababab').group())
    # ababababababababab
     
    print(re.match('(ab)*?','ababababababababab').group())
    # ''0个
     
    print(re.match('(ab)+','ababababababababab').group())
    # ababababababababab
     
    print(re.match('(ab)+?','ababababababababab').group())
    # ab
     
    print(re.match('(ab)?','ababababababababab').group())
    # ab
     
    print(re.match('(ab)??','ababababababababab').group())
    # ''0个
    1.2.8  "{m}"

    "{m}"  重复匹配m次

    1.2.9  "{m,n}"

    {m,n}    重复匹配m次或者n次(贪婪模式)

    1.2.10  "{m,n}?"

    {m,n}?   {m,n}的非贪婪模式.

    #示例:正则表达式:'(ab){1,3}','(ab){2,5}','(ab){1,3}?','(ab){2,5}?',匹配字符串:ababababababababab
    print(re.match('(ab){1,3}','ababababababababab').group())
    # ababab
     
    print(re.match('(ab){1,3}?','ababababababababab').group())
    # ab
     
    print(re.match('(ab){2,5}','ababababababababab').group())
    # ababababab
     
    print(re.match('(ab){2,5}?','ababababababababab').group())
    # abab
    1.2.11  "\\"

    "\\"     转义特殊字符或表示特殊序列

    #示例:转义特殊字符-->匹配.+?*\
     
    print(re.match('.+?*','.+?*').group())
    # sre_constants.error: multiple repeat at position 3
    说明:该错误说明我们在表达式中使用了多个表示重复功能的元字符,而不是我们原先设想的表示字符串“.+?*\”

    print(re.match('\.\+\?\*','.+?*').group())
    # .+?*
     
    # 表示特殊序列:\d 表示所有的数字,\w表示所有的字母数字
     
    print(re.match('\d*','25*29').group())
    # 25
     
    print(re.match('\w+','1134afdads').group())
    # 1134afdads
    1.2.13  "[]"

    []       表示一组字符,如果以"^"为第一个字符,表示一组字符的互补集.

    # 示例:匹配一个含有1-5的字符串
    print(re.match('[12345]+','1235425422119877').group())
    # 123542542211
     
    print(re.match('[1-5]+','1235425422119877').group())
    # 123542542211
     
    #匹配除了abc以外的所有字符
    print(re.match('[^abc]+','155acdefafdf').group())
    # 155
    1.2.13  "|"

    "|"      A|B, 选择分支,或者匹配A或者匹配B.

    # 示例:匹配由[a-z]的小写字母或[2-9]的数字组成的字符串
     
    print(re.match('[a-z]+|[2-9]+','abcdefga').group())
    # abcdefga
     
    print(re.match('[a-z]+|[2-9]+','32456546545').group())
    # 32456546545
     
    print(re.match('[a-z]+|[2-9]+','adfasf32456546545').group())
    # adfasf
     
    print(re.match('[a-z]+|[2-9]+','2356safdsfa').group())
    # 2356
     
    print(re.match('[a-z]+|[2-9]+','12356safdsfa'))
    # None
    1.3 python正则中的分组
    1.3.1:(...)

    (...)       匹配一个分组,将括号中的内容当作一个整体来对待

    # 示例:
    print(re.match('ab*','abbb').group())
    # abbb
     
    print(re.match('(ab)*','abbb').group())
    # ab
    1.3.2:(?aiLmsux)

    (?aiLmsux)    为表达式设置 A, I, L, M, S, U, 或 X 标志.标志的具体含义,会在后面的内容中详细解读

    #示例:I标志表示大小写不敏感匹配如下
     
    print(re.match('[A-Z]+','acdadsfadf'))
    # None
     
    print(re.match('[A-Z]+(?i)','acdadsfadf'))
    # <_sre.SRE_Match object; span=(0, 10), match='acdadsfadf'>
     
    #也可以这样进行设置
    print(re.match('[A-Z]+','acdadsfadf',re.I))
    # <_sre.SRE_Match object; span=(0, 10), match='acdadsfadf'>
    1.3.3:(?:...)

     (?:...)     非分组模式的分组匹配

    #示例:
    print(re.match('(?:\w+) (?:\w+)','Eric Brown').group())
    # Eric Brown
     
    # print(re.match('(?:\w+) (?:\w+)','Eric Brown').group(1))
    # IndexError: no such group
     
    print(re.match('(\w+) (\w+)','Eric Brown').group())
    # Eric Brown
     
    print(re.match('(\w+) (\w+)','Eric Brown').group(1))
    # Eric 
     
    print(re.match('(\w+) (\w+)','Eric Brown').group(2))
    # Brown
    说明:在非分组模式下,字符串的匹配只会将整个的匹配结果返回,而对于表达式中每一个组的匹配情况则不会进行保存.
    而在分组模式下,不但保存了整个分组的匹配结果,也将表达式中每一个组的匹配结果分开保存,关于Match对象的分组,后面有内容会进行详细介绍

     

    1.3.4:(?P<name>...)

    (?P<name>...)  组匹配的子字符串,可以能一个名称访问.

    # 示例:
    print(re.match('(?P<first_name>\w+) (?P<last_name>\w+)','Eric Brown').group())
    # Eric Brown
     
    print(re.match('(?P<first_name>\w+) (?P<last_name>\w+)','Eric Brown').group('first_name'))
    # Eric
     
    print(re.match('(?P<first_name>\w+) (?P<last_name>\w+)','Eric Brown').group('last_name'))
    # Brown
    1.3.5:(?P=name)

    (?P=name)     对指定的组反向的引用,以前面的以name为名称的组匹配的文本为分组内容,匹配后面的内容

    #示例:
    pattern=re.compile('(?P<number>[1-9]){5}@(?P<letters>[a-z])+\.(?P=letters)+')
     
    m=pattern.match('12345@qq.qq')
     
    print(m.group())
    # 12345@qq.qq
     
    print(m.group(1))
    # 5
     
    print(m.group(2))
    # q
    1.3.6:(?=...)

    (?=...)  当该表达式匹配成功的时候,它的前面的表达式才会匹配.

    #示例:
     
    print(re.match('\w+@(?=\d+)','abcds@123456').group())
    # abcds@
    说明:上面表达式的表示:在@后面必然跟的是数字,如果不是,则字符串不符合要求,如果是,匹配返回的内容则是前面的字符串与@

    1.3.6:(?!...)

    (?!...)   当该表达式不匹配的时候,它的前面的表达式都会匹配成功

    #示例:
    print(re.match('\w+@(?!\d+)','abcds@dfa').group())
    # abcds@
    1.3.7:(?<=...)

    (?<=...) 匹配以...开始的后面部分的字串,只能是固定的长度,也就是一个明确的表达式.

    #示例:
    print(re.match('(?<=abc)def', 'abcdef'))
    #None
     
    print(re.search('(?<=abc)def', 'abcdef'))
    #<_sre.SRE_Match object; span=(3, 6), match='def'>
    说明:该模式不能于一个字符串的开始

    print(re.match('(\w+)(?<=zhang)san', 'myzhangsan').group())
    # myzhangsan
    1.3.8:(?<!...)

    (?<!...)   匹配不是以...开始的后面部分的字串.只能是固定的长度

    print(re.match('(\w+)(?<!zhang)san', 'mylisan').group())
    # mylisan
    1.3.9:(?(id/name)yes|no)

    (?(id/name)yes|no) 如果前面以id/name表示的正则匹配,则利用yes处的正则表达式匹配后面的字符串,否则用no处的匹配,

    #示例:
    #下面的例子检查的是一个字符串两边的括号是否配对,如果没有括号或配对,则匹配成功,否则将匹配失败。
    pattern=re.compile('(?P<lefg_bracket>\()?\w+(?(lefg_bracket)\)|$)')
     
    #两边有括号
    m=pattern.match('(ab123456)')
     
    print(m.group())
    # (ab123456)
     
    #两边没有括号
    m=pattern.match('cdefghj')
     
    print(m.group())
    # cdefghj
     
    #一边有括号
    m=pattern.match('(abdcd')
     
    print(m)
    # None
    -----------------------------------------------------------------------------------------正文结束------------------------------------------------------------------

     
    ————————————————
    版权声明:本文为CSDN博主「泄特儿」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq490765184/article/details/82750191

    展开全文
  • python 正则表达式详解

    2019-04-19 15:40:00
    正则表达式是一个很强大的字符串处理工具,几乎任何关于字符串的操作都可以使用正则表达式来完成,作为一个爬虫工作者,每天和字符串打交道,正则...下面,我来介绍一下python中的正则表达式是怎么使用的。  首先...

     

      正则表达式是一个很强大的字符串处理工具,几乎任何关于字符串的操作都可以使用正则表达式来完成,作为一个爬虫工作者,每天和字符串打交道,正则表达式更是不可或缺的技能,正则表达式的在不同的语言中使用方式可能不一样,不过只要学会了任意一门语言的正则表达式用法,其他语言中大部分也只是换了个函数的名称而已,本质都是一样的。下面,我来介绍一下python中的正则表达式是怎么使用的。

      首先,python中的正则表达式大致分为以下几部分:

      1. 元字符
      2. 模式
      3. 函数
      4. re 内置对象用法
      5. 分组用法
      6. 环视用法

      所有关于正则表达式的操作都使用 python 标准库中的 re 模块。

    一、元字符 (参见 python 模块 re 文档)

      • .                    匹配任意字符(不包括换行符)
      • ^                    匹配开始位置,多行模式下匹配每一行的开始
      • $                    匹配结束位置,多行模式下匹配每一行的结束
      • *                    匹配前一个元字符0到多次
      • +                    匹配前一个元字符1到多次
      • ?                    匹配前一个元字符0到1次
      • {m,n}                匹配前一个元字符m到n次
      • \\                   转义字符,跟在其后的字符将失去作为特殊元字符的含义,例如\\.只能匹配.,不能再匹配任意字符
      • []                   字符集,一个字符的集合,可匹配其中任意一个字符
      • |                    逻辑表达式 或 ,比如 a|b 代表可匹配 a 或者 b
      • (...)                分组,默认为捕获,即被分组的内容可以被单独取出,默认每个分组有个索引,从 1 开始,按照"("的顺序决定索引值
      • (?iLmsux)            分组中可以设置模式,iLmsux之中的每个字符代表一个模式,用法参见 模式 I
      • (?:...)              分组的不捕获模式,计算索引时会跳过这个分组
      • (?P<name>...)        分组的命名模式,取此分组中的内容时可以使用索引也可以使用name
      • (?P=name)            分组的引用模式,可在同一个正则表达式用引用前面命名过的正则
      • (?#...)              注释,不影响正则表达式其它部分,用法参见 模式 I
      • (?=...)              顺序肯定环视,表示所在位置右侧能够匹配括号内正则
      • (?!...)              顺序否定环视,表示所在位置右侧不能匹配括号内正则
      • (?<=...)             逆序肯定环视,表示所在位置左侧能够匹配括号内正则
      • (?<!...)             逆序否定环视,表示所在位置左侧不能匹配括号内正则
      • (?(id/name)yes|no)   若前面指定id或name的分区匹配成功则执行yes处的正则,否则执行no处的正则
      • \number              匹配和前面索引为number的分组捕获到的内容一样的字符串
      • \A                   匹配字符串开始位置,忽略多行模式
      • \Z                   匹配字符串结束位置,忽略多行模式
      • \b                   匹配位于单词开始或结束位置的空字符串
      • \B                   匹配不位于单词开始或结束位置的空字符串
      • \d                   匹配一个数字, 相当于 [0-9]
      • \D                   匹配非数字,相当于 [^0-9]
      • \s                   匹配任意空白字符, 相当于 [ \t\n\r\f\v]
      • \S                   匹配非空白字符,相当于 [^ \t\n\r\f\v]
      • \w                   匹配数字、字母、下划线中任意一个字符, 相当于 [a-zA-Z0-9_]
      • \W                   匹配非数字、字母、下划线中的任意字符,相当于 [^a-zA-Z0-9_]

    二、模式

      • I    IGNORECASE, 忽略大小写的匹配模式, 样例如下
        复制代码
        s = 'hello World!'
        
        regex = re.compile("hello world!", re.I)
        print regex.match(s).group()
        #output> 'Hello World!'
        
        #在正则表达式中指定模式以及注释
        regex = re.compile("(?#注释)(?i)hello world!")
        print regex.match(s).group()
        #output> 'Hello World!'
        复制代码
      • L    LOCALE, 字符集本地化。这个功能是为了支持多语言版本的字符集使用环境的,比如在转义符\w,在英文环境下,它代表[a-zA-Z0-9_],即所以英文字符和数字。如果在一个法语环境下使用,缺省设置下,不能匹配"é" 或   "ç"。加上这L选项和就可以匹配了。不过这个对于中文环境似乎没有什么用,它仍然不能匹配中文字符。
      • M    MULTILINE,多行模式, 改变 ^ 和 $ 的行为
        复制代码
        s = '''first line
        second line
        third line'''
        
        # ^
        regex_start = re.compile("^\w+")
        print regex_start.findall(s)
        # output> ['first']
        
        regex_start_m = re.compile("^\w+", re.M)
        print regex_start_m.findall(s)
        # output> ['first', 'second', 'third']
        
        #$
        regex_end = re.compile("\w+$")
        print regex_end.findall(s)
        # output> ['line']
        
        regex_end_m = re.compile("\w+$", re.M)
        print regex_end_m.findall(s)
        # output> ['line', 'line', 'line']
        复制代码
      • S   DOTALL,此模式下 '.' 的匹配不受限制,可匹配任何字符,包括换行符
        复制代码
        s = '''first line
        second line
        third line'''
        #
        regex = re.compile(".+")
        print regex.findall(s)
        # output> ['first line', 'second line', 'third line']
        
        # re.S
        regex_dotall = re.compile(".+", re.S)
        print regex_dotall.findall(s)
        # output> ['first line\nsecond line\nthird line']
        复制代码
      • X    VERBOSE,冗余模式, 此模式忽略正则表达式中的空白和#号的注释,例如写一个匹配邮箱的正则表达式
        email_regex = re.compile("[\w+\.]+@[a-zA-Z\d]+\.(com|cn)")
        
        email_regex = re.compile("""[\w+\.]+  # 匹配@符前的部分
                                    @  # @符
                                    [a-zA-Z\d]+  # 邮箱类别
                                    \.(com|cn)   # 邮箱后缀  """, re.X)

     

      • U    UNICODE,使用 \w, \W, \b, \B 这些元字符时将按照 UNICODE 定义的属性.

    正则表达式的模式是可以同时使用多个的,在 python 里面使用按位或运算符 | 同时添加多个模式

    如 re.compile('', re.I|re.M|re.S)

    每个模式在 re 模块中其实就是不同的数字

    复制代码
    print re.I
    # output> 2
    print re.L
    # output> 4
    print re.M
    # output> 8
    print re.S
    # output> 16
    print re.X
    # output> 64
    print re.U
    # output> 32
    复制代码

    三、函数 (参见 python 模块 re 文档)

      python 的 re 模块提供了很多方便的函数使你可以使用正则表达式来操作字符串,每种函数都有它自己的特性和使用场景,熟悉之后对你的工作会有很大帮助

      • compile(pattern, flags=0)   

    给定一个正则表达式 pattern,指定使用的模式 flags 默认为0 即不使用任何模式,然后会返回一个 SRE_Pattern (参见 第四小节 re 内置对象用法) 对象

    regex = re.compile(".+")
    print regex
    # output> <_sre.SRE_Pattern object at 0x00000000026BB0B8>

    这个对象可以调用其他函数来完成匹配,一般来说推荐使用 compile 函数预编译出一个正则模式之后再去使用,这样在后面的代码中可以很方便的复用它,当然大部分函数也可以不用 compile 直接使用,具体见 findall 函数

    复制代码
    s = '''first line
    second line
    third line'''
    #
    regex = re.compile(".+")
    # 调用 findall 函数
    print regex.findall(s)
    # output> ['first line', 'second line', 'third line']
    # 调用 search 函数
    print regex.search(s).group()
    # output> first lin
    复制代码
      • escape(pattern)   

    转义 如果你需要操作的文本中含有正则的元字符,你在写正则的时候需要将元字符加上反斜扛 \ 去匹配自身, 而当这样的字符很多时,写出来的正则表达式就看起来很乱而且写起来也挺麻烦的,这个时候你可以使用这个函数,用法如下

    复制代码
    s = ".+\d123"
    #
    regex_str = re.escape(".+\d123")
    # 查看转义后的字符
    print regex_str
    # output> \.\+\\d123
    
    # 查看匹配到的结果
    for g in re.findall(regex_str, s):
        print g
    # output> .+\d123
    复制代码
      • findall(pattern, string, flags=0)   

    参数 pattern 为正则表达式, string 为待操作字符串, flags 为所用模式,函数作用为在待操作字符串中寻找所有匹配正则表达式的字串,返回一个列表,如果没有匹配到任何子串,返回一个空列表。

    复制代码
    s = '''first line
    second line
    third line'''
    
    # compile 预编译后使用 findall
    regex = re.compile("\w+")
    print regex.findall(s)
    # output> ['first', 'line', 'second', 'line', 'third', 'line']
    
    # 不使用 compile 直接使用 findall
    print re.findall("\w+", s)
    # output> ['first', 'line', 'second', 'line', 'third', 'line']
    复制代码
      • finditer(pattern, string, flags=0)   

    参数和作用与 findall 一样,不同之处在于 findall 返回一个列表, finditer 返回一个迭代器(参见 http://www.cnblogs.com/huxi/archive/2011/07/01/2095931.html ), 而且迭代器每次返回的值并不是字符串,而是一个 SRE_Match (参见 第四小节 re 内置对象用法) 对象,这个对象的具体用法见 match 函数。

    复制代码
    s = '''first line
    second line
    third line'''
    
    regex = re.compile("\w+")
    print regex.finditer(s)
    # output> <callable-iterator object at 0x0000000001DF3B38>
    for i in regex.finditer(s):
        print i
    # output> <_sre.SRE_Match object at 0x0000000002B7A920>
    #         <_sre.SRE_Match object at 0x0000000002B7A8B8>
    #         <_sre.SRE_Match object at 0x0000000002B7A920>
    #         <_sre.SRE_Match object at 0x0000000002B7A8B8>
    #         <_sre.SRE_Match object at 0x0000000002B7A920>
    #         <_sre.SRE_Match object at 0x0000000002B7A8B8>
    复制代码
      • match(pattern, string, flags=0)   

    使用指定正则去待操作字符串中寻找可以匹配的子串, 返回匹配上的第一个字串,并且不再继续找,需要注意的是 match 函数是从字符串开始处开始查找的,如果开始处不匹配,则不再继续寻找,返回值为 一个 SRE_Match(参见 第四小节 re 内置对象用法) 对象,找不到时返回 None

    复制代码
    s = '''first line
    second line
    third line'''
    
    # compile
    regex = re.compile("\w+")
    m = regex.match(s)
    print m
    # output> <_sre.SRE_Match object at 0x0000000002BCA8B8>
    print m.group()
    # output> first
    
    # s 的开头是 "f", 但正则中限制了开始为 i 所以找不到
    regex = re.compile("^i\w+")
    print regex.match(s)
    # output> None
    
    复制代码
      • purge()   

    当你在程序中使用 re 模块,无论是先使用 compile 还是直接使用比如 findall 来使用正则表达式操作文本,re 模块都会将正则表达式先编译一下, 并且会将编译过后的正则表达式放到缓存中,这样下次使用同样的正则表达式的时候就不需要再次编译, 因为编译其实是很费时的,这样可以提升效率,而默认缓存的正则表达式的个数是 100, 当你需要频繁使用少量正则表达式的时候,缓存可以提升效率,而使用的正则表达式过多时,缓存带来的优势就不明显了 (参考 《python re.compile对性能的影响http://blog.trytofix.com/article/detail/13/), 这个函数的作用是清除缓存中的正则表达式,可能在你需要优化占用内存的时候会用到。

      • search(pattern, string, flags=0)   

    函数类似于 match,不同之处在于不限制正则表达式的开始匹配位置

    复制代码
    s = '''first line
    second line
    third line'''
    
    # 需要从开始处匹配 所以匹配不到 
    print re.match('i\w+', s)
    # output> None
    
    # 没有限制起始匹配位置
    print re.search('i\w+', s)
    # output> <_sre.SRE_Match object at 0x0000000002C6A920>
    
    print re.search('i\w+', s).group()
    # output> irst
    复制代码
      • split(pattern, string, maxsplit=0, flags=0)   

    参数 maxsplit 指定切分次数, 函数使用给定正则表达式寻找切分字符串位置,返回包含切分后子串的列表,如果匹配不到,则返回包含原字符串的一个列表

    复制代码
    s = '''first 111 line
    second 222 line
    third 333 line'''
    
    # 按照数字切分
    print re.split('\d+', s)
    # output> ['first ', ' line\nsecond ', ' line\nthird ', ' line']
    
    # \.+ 匹配不到 返回包含自身的列表
    print re.split('\.+', s, 1)
    # output> ['first 111 line\nsecond 222 line\nthird 333 line']
    
    # maxsplit 参数
    print re.split('\d+', s, 1)
    # output> ['first ', ' line\nsecond 222 line\nthird 333 line']
    复制代码

     

      • sub(pattern, repl, string, count=0, flags=0)   

    替换函数,将正则表达式 pattern 匹配到的字符串替换为 repl 指定的字符串,  参数 count 用于指定最大替换次数

    复制代码
    s = "the sum of 7 and 9 is [7+9]."
    
    # 基本用法 将目标替换为固定字符串
    print re.sub('\[7\+9\]', '16', s)
    # output> the sum of 7 and 9 is 16.
    
    # 高级用法 1 使用前面匹配的到的内容 \1 代表 pattern 中捕获到的第一个分组的内容
    print re.sub('\[(7)\+(9)\]', r'\2\1', s)
    # output> the sum of 7 and 9 is 97.
    
    
    # 高级用法 2 使用函数型 repl 参数, 处理匹配到的 SRE_Match 对象
    def replacement(m):
        p_str = m.group()
        if p_str == '7':
            return '77'
        if p_str == '9':
            return '99'
        return ''
    print re.sub('\d', replacement, s)
    # output> the sum of 77 and 99 is [77+99].
    
    
    # 高级用法 3 使用函数型 repl 参数, 处理匹配到的 SRE_Match 对象 增加作用域 自动计算
    scope = {}
    example_string_1 = "the sum of 7 and 9 is [7+9]."
    example_string_2 = "[name = 'Mr.Gumby']Hello,[name]"
    
    def replacement(m):
        code = m.group(1)
        st = ''
        try:
            st = str(eval(code, scope))
        except SyntaxError:
            exec code in scope
        return st
    
    # 解析: code='7+9'
    #       str(eval(code, scope))='16'
    print re.sub('\[(.+?)\]', replacement, example_string_1)
    # output> the sum of 7 and 9 is 16.
    
    # 两次替换 # 解析1: code="name = 'Mr.Gumby'" # eval(code) # raise SyntaxError # exec code in scope # 在命名空间 scope 中将 "Mr.Gumby" 赋给了变量 name # 解析2: code="name" # eval(name) 返回变量 name 的值 Mr.Gumby print re.sub('\[(.+?)\]', replacement, example_string_2) # output> Hello,Mr.Gumby
    复制代码
      • subn(pattern, repl, string, count=0, flags=0)   

    作用与函数 sub 一样, 唯一不同之处在于返回值为一个元组,第一个值为替换后的字符串,第二个值为发生替换的次数

      • template(pattern, flags=0)   

    这个吧,咋一看和 compile 差不多,不过不支持 +、?、*、{} 等这样的元字符,只要是需要有重复功能的元字符,就不支持,查了查资料,貌似没人知道这个函数到底是干嘛的...

     

      四、re 内置对象用法

      • SRE_Pattern    这个对象是一个编译后的正则表达式,编译后不仅能够复用和提升效率,同时也能够获得一些其他的关于正则表达式的信息

    属性:

    • flags         编译时指定的模式
    • groupindex    以正则表达式中有别名的组的别名为键、以该组对应的编号为值的字典,没有别名的组不包含在内。
    • groups        正则表达式中分组的数量
    • pattern       编译时用的正则表达式
      复制代码
      s = 'Hello, Mr.Gumby : 2016/10/26'
      p = re.compile('''(?:        # 构造一个不捕获分组 用于使用 |
                    (?P<name>\w+\.\w+)    # 匹配 Mr.Gumby
                    |     # 或
                    (?P<no>\s+\.\w+) # 一个匹配不到的命名分组
                    )
                    .*? # 匹配  : 
                    (\d+) # 匹配 2016
                    ''', re.X)
      
      #
      print p.flags
      # output> 64
      print p.groupindex
      # output> {'name': 1, 'no': 2}
      print p.groups
      # output> 3
      print p.pattern
      # output> (?:        # 构造一个不捕获分组 用于使用 |
      #              (?P<name>\w+\.\w+)    # 匹配 Mr.Gumby
      #              |     # 或
      #              (?P<no>\s+\.\w+) # 一个匹配不到的命名分组
      #              )
      #              .*? # 匹配  : 
      #              (\d+) # 匹配 2016
      复制代码

       

    函数:可使用 findall、finditer、match、search、split、sub、subn 等函数

     

      • SRE_Match    这个对象会保存本次匹配的结果,包含很多关于匹配过程以及匹配结果的信息

    属性:

    • endpos       本次搜索结束位置索引
    • lastgroup    本次搜索匹配到的最后一个分组的别名
    • lastindex    本次搜索匹配到的最后一个分组的索引
    • pos          本次搜索开始位置索引
    • re           本次搜索使用的 SRE_Pattern 对象
    • regs         列表,元素为元组,包含本次搜索匹配到的所有分组的起止位置
    • string       本次搜索操作的字符串
      复制代码
      s = 'Hello, Mr.Gumby : 2016/10/26'
      m = re.search(', (?P<name>\w+\.\w+).*?(\d+)', s)
      # 本次搜索的结束位置索引
      print m.endpos
      # output> 28

      # 本次搜索匹配到的最后一个分组的别名
      # 本次匹配最后一个分组没有别名
      print m.lastgroup
      # output> None

      # 本次搜索匹配到的最后一个分组的索引
      print m.lastindex
      # output> 2

      # 本次搜索开始位置索引
      print m.pos
      # output> 0

      # 本次搜索使用的 SRE_Pattern 对象
      print m.re
      # output> <_sre.SRE_Pattern object at 0x000000000277E158>

      # 列表,元素为元组,包含本次搜索匹配到的所有分组的起止位置 第一个元组为正则表达式匹配范围
      print m.regs
      # output> ((7, 22), (7, 15), (18, 22))

      # 本次搜索操作的字符串
      print m.string
      # output> Hello, Mr.Gumby : 2016/10/26
      复制代码

       

    函数:

    • end([group=0])               返回指定分组的结束位置,默认返回正则表达式所匹配到的最后一个字符的索引
    • expand(template)             根据模版返回相应的字符串,类似与 sub 函数里面的 repl, 可使用 \1 或者 \g<name> 来选择分组
    • group([group1, ...])         根据提供的索引或名字返回响应分组的内容,默认返回 start() 到 end() 之间的字符串, 提供多个参数将返回一个元组
    • groupdict([default=None])    返回 返回一个包含所有匹配到的命名分组的字典,没有命名的分组不包含在内,key 为组名, value 为匹配到的内容,参数 default 为没有参与本次匹配的命名分组提供默认值
    • groups([default=None])       以元组形式返回每一个分组匹配到的字符串,包括没有参与匹配的分组,其值为 default
    • span([group])                返回指定分组的起止位置组成的元组,默认返回由 start() 和 end() 组成的元组
    • start([group])               返回指定分组的开始位置,默认返回正则表达式所匹配到的第一个字符的索引
      复制代码
      s = 'Hello, Mr.Gumby : 2016/10/26'
      m = re.search('''(?:        # 构造一个不捕获分组 用于使用 |
                    (?P<name>\w+\.\w+)    # 匹配 Mr.Gumby
                    |     # 或
                    (?P<no>\s+\.\w+) # 一个匹配不到的命名分组
                    )
                    .*? # 匹配  : 
                    (\d+) # 匹配 2016
                    ''',
                    s, re.X)
      
      # 返回指定分组的结束位置,默认返回正则表达式所匹配到的最后一个字符的索引
      print m.end()
      # output> 22
      
      # 根据模版返回相应的字符串,类似与 sub 函数里面的 repl, 可使用 \1 或者 \g<name> 来选择分组
      print m.expand("my name is \\1")
      # output> my name is Mr.Gumby
      
      # 根据提供的索引或名字返回响应分组的内容,默认返回 start() 到 end() 之间的字符串, 提供多个参数将返回一个元组
      print m.group()
      # output> Mr.Gumby : 2016
      print m.group(1,2)
      # output> ('Mr.Gumby', None)
      
      # 返回 返回一个包含所有匹配到的命名分组的字典,没有命名的分组不包含在内,key 为组名, value 为匹配到的内容,参数 default 为没有参与本次匹配的命名分组提供默认值
      print m.groupdict('default_string')
      # output> {'name': 'Mr.Gumby', 'no': 'default_string'}
      
      # 以元组形式返回每一个分组匹配到的字符串,包括没有参与匹配的分组,其值为 default
      print m.groups('default_string')
      # output> ('Mr.Gumby', 'default_string', '2016')
      
      # 返回指定分组的起止未知组成的元组,默认返回由 start() 和 end() 组成的元组
      print m.span(3)
      # output> (18, 22)
      
      # 返回指定分组的开始位置,默认返回正则表达式所匹配到的第一个字符的索引
      print m.start(3)
      # output> 18
      复制代码

    五、分组用法

        python 的正则表达式中用小括号 "(" 表示分组,按照每个分组中前半部分出现的顺序 "(" 判定分组的索引,索引从 1 开始,每个分组在访问的时候可以使用索引,也可以使用别名

    复制代码
    s = 'Hello, Mr.Gumby : 2016/10/26'
    p = re.compile("(?P<name>\w+\.\w+).*?(\d+)(?#comment)")
    m = p.search(s)
    
    # 使用别名访问
    print m.group('name')
    # output> Mr.Gumby
    # 使用分组访问
    print m.group(2)
    # output> 2016
    复制代码

        有时候可能只是为了把正则表达式分组,而不需要捕获其中的内容,这时候可以使用非捕获分组

    复制代码
    s = 'Hello, Mr.Gumby : 2016/10/26'
    p = re.compile("""
                    (?:  # 非捕获分组标志 用于使用 |
                        (?P<name>\w+\.\w+)
                        |
                        (\d+/)
                    )
                    """, re.X)
    m = p.search(s)
    # 使用非捕获分组
    # 此分组将不计入 SRE_Pattern 的 分组计数
    print p.groups
    # output> 2
    
    # 不计入 SRE_Match 的分组
    print m.groups()
    # output> ('Mr.Gumby', None)
    复制代码

        如果你在写正则的时候需要在正则里面重复书写某个表达式,那么你可以使用正则的引用分组功能,需要注意的是引用的不是前面分组的 正则表达式 而是捕获到的 内容,并且引用的分组不算在分组总数中.

    复制代码
    s = 'Hello, Mr.Gumby : 2016/2016/26'
    p = re.compile("""
                    (?:  # 非捕获分组标志 用于使用 |
                        (?P<name>\w+\.\w+)
                        |
                        (\d+/)
                    )
                    .*?(?P<number>\d+)/(?P=number)/
                    """, re.X)
    m = p.search(s)
    # 使用引用分组
    # 此分组将不计入 SRE_Pattern 的 分组计数
    print p.groups
    # output> 3
    
    # 不计入 SRE_Match 的分组
    print m.groups()
    # output> ('Mr.Gumby', None, '2016')
    
    # 查看匹配到的字符串
    print m.group()
    # output> Mr.Gumby : 2016/2016/
    复制代码

     

    六、环视用法

    环视还有其他的名字,例如 界定、断言、预搜索等,叫法不一。

    环视是一种特殊的正则语法,它匹配的不是字符串,而是 位置,其实就是使用正则来说明这个位置的左右应该是什么或者应该不是什么,然后去寻找这个位置。

    环视的语法有四种,见第一小节元字符,基本用法如下。

    复制代码
    s = 'Hello, Mr.Gumby : 2016/10/26  Hello,r.Gumby : 2016/10/26'
    
    # 不加环视限定
    print re.compile("(?P<name>\w+\.\w+)").findall(s)
    # output> ['Mr.Gumby', 'r.Gumby']
    
    # 环视表达式所在位置 左边为 "Hello, "
    print re.compile("(?<=Hello, )(?P<name>\w+\.\w+)").findall(s)
    # output> ['Mr.Gumby']
    
    # 环视表达式所在位置 左边不为 ","
    print re.compile("(?<!,)(?P<name>\w+\.\w+)").findall(s)
    # output> ['Mr.Gumby']
    
    # 环视表达式所在位置 右边为 "M"
    print re.compile("(?=M)(?P<name>\w+\.\w+)").findall(s)
    # output> ['Mr.Gumby']
    
    # 环视表达式所在位置 右边不为 r
    print re.compile("(?!r)(?P<name>\w+\.\w+)").findall(s)
    # output> ['Mr.Gumby']
    复制代码

    高级一些的例子参见《正则基础之——环视(Lookaround)》(http://www.cnblogs.com/kernel0815/p/3375249.html)

     

    参考文章:

    Python正则表达式指南》(http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html)

    Python 正则式学习笔记 》(http://blog.csdn.net/whycadi/article/details/2011046)

     

    本文转自:https://www.cnblogs.com/dyfblog/p/5880728.html

    转载于:https://www.cnblogs.com/misswangxing/p/10736310.html

    展开全文
  • Python正则表达式操作指南 原文出处:http://www.amk.ca/python/howto/regex/ 原文作者:A.M. Kuchling (amk@amk.ca) 授权许可:创作共用协议 翻译人员:FireHare 校对人员:Leal 适用版本:Python ...

    Python正则表达式操作指南

    原文出处:http://www.amk.ca/python/howto/regex/

    原文作者:A.M. Kuchling (amk@amk.ca)

    授权许可:创作共用协议

    翻译人员:FireHare

    校对人员:Leal

    适用版本:Python 1.5 及后续版本


    摘要


    本文是通过Python的 re 模块来使用正则表达式的一个入门教程,和库参考手册的对应章节相比,更为浅显易懂、循序渐进。

    本文可以从 http://www.amk.ca/python/howto 捕获


    目录


    简介

    Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。Python 1.5之前版本则是通过 regex 模块提供 Emacs 风格的模式。Emacs 风格模式可读性稍差些,而且功能也不强,因此编写新代码时尽量不要再使用 regex 模块,当然偶尔你还是可能在老代码里发现其踪影。


    就其本质而言,正则表达式(或 RE)是一种小型的、高度专业化的编程语言,(在Python中)它内嵌在Python中,并通过 re 模块实现。使用这个小型语言,你可以为想要匹配的相应字符串集指定规则;该字符串集可能包含英文语句、e-mail地址、TeX命令或任何你想搞定的东西。然后你可以问诸如“这个字符串匹配该模式吗?”或“在这个字符串中是否有部分匹配该模式呢?”。你也可以使用 RE 以各种方式来修改或分割字符串。


    正则表达式模式被编译成一系列的字节码,然后由用 C 编写的匹配引擎执行。在高级用法中,也许还要仔细留意引擎是如何执行给定 RE ,如何以特定方式编写 RE 以令生产的字节码运行速度更快。本文并不涉及优化,因为那要求你已充分掌握了匹配引擎的内部机制。


    正则表达式语言相对小型和受限(功能有限),因此并非所有字符串处理都能用正则表达式完成。当然也有些任务可以用正则表达式完成,不过最终表达式会变得异常复杂。碰到这些情形时,编写 Python 代码进行处理可能反而更好;尽管 Python 代码比一个精巧的正则表达式要慢些,但它更易理解。

    简单模式

    我们将从最简单的正则表达式学习开始。由于正则表达式常用于字符串操作,那我们就从最常见的任务:字符匹配 下手。


    有关正则表达式底层的计算机科学上的详细解释(确定性和非确定性有限自动机),你可以查阅编写编译器相关的任何教科书。

    字符匹配

    大多数字母和字符一般都会和自身匹配。例如,正则表达式 test 会和字符串“test”完全匹配。(你也可以使用大小写不敏感模式,它还能让这个 RE 匹配“Test”或“TEST”;稍后会有更多解释。)

    这个规则当然会有例外;有些字符比较特殊,它们和自身并不匹配,而是会表明应和一些特殊的东西匹配,或者它们会影响到 RE 其它部分的重复次数。本文很大篇幅专门讨论了各种元字符及其作用。

    这里有一个元字符的完整列表;其含义会在本指南余下部分进行讨论。

    . ^ $ * + ? { [ ] \ | ( )

    我们首先考察的元字符是"[" 和 "]"。它们常用来指定一个字符类别,所谓字符类别就是你想匹配的一个字符集。字符可以单个列出,也可以用“-”号分隔的两个给定字符来表示一个字符区间。例如,[abc] 将匹配"a", "b", 或 "c"中的任意一个字符;也可以用区间[a-c]来表示同一字符集,和前者效果一致。如果你只想匹配小写字母,那么 RE 应写成 [a-z].

    元字符在类别里并不起作用。例如,[akm$]将匹配字符"a", "k", "m", 或 "$" 中的任意一个;"$"通常用作元字符,但在字符类别里,其特性被除去,恢复成普通字符。

    你可以用补集来匹配不在区间范围内的字符。其做法是把"^"作为类别的首个字符;其它地方的"^"只会简单匹配 "^"字符本身。例如,[^5] 将匹配除 "5" 之外的任意字符。

    也许最重要的元字符是反斜杠"\"。 做为 Python 中的字符串字母,反斜杠后面可以加不同的字符以表示不同特殊意义。它也可以用于取消所有的元字符,这样你就可以在模式中匹配它们了。举个例子,如果你需要匹配字符 "[" 或 "\",你可以在它们之前用反斜杠来取消它们的特殊意义: \[ 或 \\。

    一些用 "\" 开始的特殊字符所表示的预定义字符集通常是很有用的,象数字集,字母集,或其它非空字符集。下列是可用的预设特殊字符:

    \d  匹配任何十进制数;它相当于类 [0-9]。
    \D  匹配任何非数字字符;它相当于类 [^0-9]。
    \s  匹配任何空白字符;它相当于类  [ \t\n\r\f\v]。
    \S  匹配任何非空白字符;它相当于类 [^ \t\n\r\f\v]。
    \w  匹配任何字母数字字符;它相当于类 [a-zA-Z0-9_]。
    \W  匹配任何非字母数字字符;它相当于类 [^a-zA-Z0-9_]。
    


    这样特殊字符都可以包含在一个字符类中。如,[\s,.]字符类将匹配任何空白字符或","或"."。

    本节最后一个元字符是 . 。它匹配除了换行字符外的任何字符,在 alternate 模式(re.DOTALL)下它甚至可以匹配换行。"." 通常被用于你想匹配“任何字符”的地方。

    重复

    正则表达式第一件能做的事是能够匹配不定长的字符集,而这是其它能作用在字符串上的方法所不能做到的。 不过,如果那是正则表达式唯一的附加功能的话,那么它们也就不那么优秀了。它们的另一个功能就是你可以指定正则表达式的一部分的重复次数。

    我们讨论的第一个重复功能的元字符是 *。* 并不匹配字母字符 "*";相反,它指定前一个字符可以被匹配零次或更多次,而不是只有一次。

    举个例子,ca*t 将匹配 "ct" (0 个 "a" 字符), "cat" (1 个 "a"), "caaat" (3 个 "a" 字符)等等。RE 引擎有各种来自 C 的整数类型大小的内部限制,以防止它匹配超过20亿个 "a" 字符;你也许没有足够的内存去建造那么大的字符串,所以将不会累计到那个限制。

    象 * 这样地重复是“贪婪的”;当重复一个 RE 时,匹配引擎会试着重复尽可能多的次数。如果模式的后面部分没有被匹配,匹配引擎将退回并再次尝试更小的重复。


    一步步的示例可以使它更加清晰。让我们考虑表达式 a[bcd]*b。它匹配字母 "a",零个或更多个来自类 [bcd]中的字母,最后以 "b" 结尾。现在想一想该 RE 对字符串 "abcbd" 的匹配。

    Step Matched Explanation
    1 a a 匹配模式
    2 abcbd 引擎匹配 [bcd]*,并尽其所能匹配到字符串的结尾
    3 Failure 引擎尝试匹配 b,但当前位置已经是字符的最后了,所以失败
    4 abcb 退回,[bcd]*尝试少匹配一个字符。
    5 Failure 再次尝次b,但在当前最后一位字符是"d"。
    6 abc 再次退回,[bcd]*只匹配 "bc"。
    7 abcb 再次尝试 b ,这次当前位上的字符正好是 "b"

    RE 的结尾部分现在可以到达了,它匹配 "abcb"。这证明了匹配引擎一开始会尽其所能进行匹配,如果没有匹配然后就逐步退回并反复尝试 RE 剩下来的部分。直到它退回尝试匹配 [bcd] 到零次为止,如果随后还是失败,那么引擎就会认为该字符串根本无法匹配 RE 。


    另一个重复元字符是 +,表示匹配一或更多次。请注意 * 和 + 之间的不同;* 匹配零或更多次,所以可以根本就不出现,而 + 则要求至少出现一次。用同一个例子,ca+t 就可以匹配 "cat" (1 个 "a"), "caaat" (3 个 "a"), 但不能匹配 "ct"。


    还有更多的限定符。问号 ? 匹配一次或零次;你可以认为它用于标识某事物是可选的。例如:home-?brew 匹配 "homebrew" 或 "home-brew"。


    最复杂的重复限定符是 {m,n}(注意m,n之间不能有空格),其中 m 和 n 是十进制整数。该限定符的意思是至少有 m 个重复,至多到 n 个重复。举个例子,a/{1,3}b 将匹配 "a/b","a//b" 和 "a///b"。它不能匹配 "ab" 因为没有斜杠,也不能匹配 "ab" ,因为有四个。


    你可以忽略 m 或 n;因为会为缺失的值假设一个合理的值。忽略 m 会认为下边界是 0,而忽略 n 的结果将是上边界为无穷大 -- 实际上是先前我们提到的20亿,但这也许同无穷大一样。


    细心的读者也许注意到其他三个限定符都可以用这样方式来表示。 {0,} 等同于 *,{1,} 等同于 +,而{0,1}则与 ? 相同。如果可以的话,最好使用 *,+,或?。很简单因为它们更短也更容易懂。

    使用正则表达式

    现在我们已经看了一些简单的正则表达式,那么我们实际在 Python 中是如何使用它们的呢? re 模块提供了一个正则表达式引擎的接口,可以让你将 REs 编译成对象并用它们来进行匹配。

    编译正则表达式

    正则表达式被编译成 `RegexObject` 实例,可以为不同的操作提供方法,如模式匹配搜索或字符串替换。

    #python
    >>> import re
    >>> p = re.compile('ab*')
    >>> print p
    <_sre.SRE_Pattern object at 0xb76e1a70>
    

    re.compile() 也接受可选的标志参数,常用来实现不同的特殊功能和语法变更。我们稍后将查看所有可用的设置,但现在只举一个例子:

    #!python
    >>> p = re.compile('ab*', re.IGNORECASE)
    

    RE 被做为一个字符串发送给 re.compile()。REs 被处理成字符串是因为正则表达式不是 Python 语言的核心部分,也没有为它创建特定的语法。(应用程序根本就不需要 REs,因此没必要包含它们去使语言说明变得臃肿不堪。)而 re 模块则只是以一个 C 扩展模块的形式来被 Python 包含,就象 socket 或 zlib 模块一样


    将 REs 作为字符串以保证 Python 语言的简洁,但这样带来的一个麻烦就是象下节标题所讲的。

    反斜杠的麻烦

    在早期规定中,正则表达式用反斜杠字符 ("\") 来表示特殊格式或允许使用特殊字符而不调用它的特殊用法。这就与 Python 在字符串中的那些起相同作用的相同字符产生了冲突。


    让我们举例说明,你想写一个 RE 以匹配字符串 "\section",可能是在一个 LATEX 文件查找。为了要在程序代码中判断,首先要写出想要匹配的字符串。接下来你需要在所有反斜杠和其它元字符前加反斜杠来取消其特殊意义,结果要匹配的字符串就成了"\\section"。 当把这个字符串传递给re.compile()时必须还是"\\section"。然而,作为Python的字符串实值(string literals)来表示的话,"\\section"中两个反斜杠还要再次取消特殊意义,最后结果就变成了"\\\\section"。

    字符 阶段
    \section 要匹配的字符串
    \\section 为 re.compile 取消反斜杠的特殊意义
    "\\\\section" 为"\\section"的字符串实值(string literals)取消反斜杠的特殊意义


    简单地说,为了匹配一个反斜杠,不得不在 RE 字符串中写 '\\\\',因为正则表达式中必须是 "\\",而每个反斜杠在常规的 Python 字符串实值中必须表示成 "\\"。在 REs 中反斜杠的这个重复特性会导致大量重复的反斜杠,而且所生成的字符串也很难懂。

    解决的办法就是为正则表达式使用 Python 的 raw 字符串表示;在字符串前加个 "r" 反斜杠就不会被任何特殊方式处理,所以 r"\n" 就是包含"\" 和 "n" 的两个字符,而 "\n" 则是一个字符,表示一个换行。正则表达式通常在 Python 代码中都是用这种 raw 字符串表示。

    常规字符串 Raw 字符串
    "ab*" r"ab*"
    "\\\\section" r"\\section"
    "\\w+\\s+\\1" r"\w+\s+\1"

    执行匹配

    一旦你有了已经编译了的正则表达式的对象,你要用它做什么呢?`RegexObject` 实例有一些方法和属性。这里只显示了最重要的几个,如果要看完整的列表请查阅 Python Library Reference

    方法/属性 作用
    match() 决定 RE 是否在字符串刚开始的位置匹配
    search() 扫描字符串,找到这个 RE 匹配的位置
    findall() 找到 RE 匹配的所有子串,并把它们作为一个列表返回
    finditer() 找到 RE 匹配的所有子串,并把它们作为一个迭代器返回


    如果没有匹配到的话,match() 和 search() 将返回 None。如果成功的话,就会返回一个 `MatchObject` 实例,其中有这次匹配的信息:它是从哪里开始和结束,它所匹配的子串等等。


    你可以用采用人机对话并用 re 模块实验的方式来学习它。如果你有 Tkinter 的话,你也许可以考虑参考一下 Tools/scripts/redemo.py,一个包含在 Python 发行版里的示范程序。


    首先,运行 Python 解释器,导入 re 模块并编译一个 RE:

    #!python
    Python 2.2.2 (#1, Feb 10 2003, 12:57:01)
    >>> import re
    >>> p = re.compile('[a-z]+')
    >>> p
    <_sre.SRE_Pattern object at 80c3c28>
    

    现在,你可以试着用 RE 的 [a-z]+ 去匹配不同的字符串。一个空字符串将根本不能匹配,因为 + 的意思是 “一个或更多的重复次数”。 在这种情况下 match() 将返回 None,因为它使解释器没有输出。你可以明确地打印出 match() 的结果来弄清这一点。

    #!python
    >>> p.match("")
    >>> print p.match("")
    None
    

    现在,让我们试着用它来匹配一个字符串,如 "tempo"。这时,match() 将返回一个 MatchObject。因此你可以将结果保存在变量里以便後面使用。

    #!python
    >>> m = p.match( 'tempo')
    >>> print m
    <_sre.SRE_Match object at 80c4f68>
    

    现在你可以查询 `MatchObject` 关于匹配字符串的相关信息了。MatchObject 实例也有几个方法和属性;最重要的那些如下所示:

    方法/属性 作用
    group() 返回被 RE 匹配的字符串
    start() 返回匹配开始的位置
    end() 返回匹配结束的位置
    span() 返回一个元组包含匹配 (开始,结束) 的位置


    试试这些方法不久就会清楚它们的作用了:

    #!python
    >>> m.group()
    'tempo'
    >>> m.start(), m.end()
    (0, 5)
    >>> m.span()
    (0, 5)
    

    group() 返回 RE 匹配的子串。start() 和 end() 返回匹配开始和结束时的索引。span() 则用单个元组把开始和结束时的索引一起返回。因为匹配方法检查到如果 RE 在字符串开始处开始匹配,那么 start() 将总是为零。然而, `RegexObject` 实例的 search 方法扫描下面的字符串的话,在这种情况下,匹配开始的位置就也许不是零了。

    #!python
    >>> print p.match('::: message')
    None
    >>> m = p.search('::: message') ; print m
    <re.MatchObject instance at 80c9650>
    >>> m.group()
    'message'
    >>> m.span()
    (4, 11)
    

    在实际程序中,最常见的作法是将 `MatchObject` 保存在一个变量里,然後检查它是否为 None,通常如下所示:

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

    两个 `RegexObject` 方法返回所有匹配模式的子串。findall()返回一个匹配字符串行表:

    #!python
    >>> p = re.compile('\d+')
    >>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
    ['12', '11', '10']
    

    findall() 在它返回结果时不得不创建一个列表。在 Python 2.2中,也可以用 finditer() 方法。

    #!python
    >>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')
    >>> iterator
    <callable-iterator object at 0x401833ac>
    >>> for match in iterator:
    ...     print match.span()
    ...
    (0, 2)
    (22, 24)
    (29, 31)
    

    模块级函数

    你不一定要产生一个 `RegexObject` 对象然后再调用它的方法;re 模块也提供了顶级函数调用如 match()、search()、sub() 等等。这些函数使用 RE 字符串作为第一个参数,而后面的参数则与相应 `RegexObject` 的方法参数相同,返回则要么是 None 要么就是一个 `MatchObject` 的实例。

    #!python
    >>> print re.match(r'From\s+', 'Fromage amk')
    None
    >>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')
    <re.MatchObject instance at 80c5978>
    

    Under the hood, 这些函数简单地产生一个 RegexOject 并在其上调用相应的方法。它们也在缓存里保存编译后的对象,因此在将来调用用到相同 RE 时就会更快。


    你将使用这些模块级函数,还是先得到一个 `RegexObject` 再调用它的方法呢?如何选择依赖于怎样用 RE 更有效率以及你个人编码风格。如果一个 RE 在代码中只做用一次的话,那么模块级函数也许更方便。如果程序包含很多的正则表达式,或在多处复用同一个的话,那么将全部定义放在一起,在一段代码中提前编译所有的 REs 更有用。从标准库中看一个例子,这是从 xmllib.py 文件中提取出来的:

    #!python
    ref = re.compile( ... )
    entityref = re.compile( ... )
    charref = re.compile( ... )
    starttagopen = re.compile( ... )
    

    我通常更喜欢使用编译对象,甚至它只用一次,但很少人会像我这样做(如同一个纯粹主义者)。

    编译标志

    编译标志让你可以修改正则表达式的一些运行方式。在 re 模块中标志可以使用两个名字,一个是全名如 IGNORECASE,一个是缩写,一字母形式如 I。(如果你熟悉 Perl 的模式修改,一字母形式使用同样的字母;例如 re.VERBOSE的缩写形式是 re.X。)多个标志可以通过按位 OR-ing 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:


    这有个可用标志表,对每个标志后面都有详细的说明。

    标志 含义
    DOTALL, S 使 . 匹配包括换行在内的所有字符
    IGNORECASE, I 使匹配对大小写不敏感
    LOCALE, L 做本地化识别(locale-aware)匹配
    MULTILINE, M 多行匹配,影响 ^ 和 $
    VERBOSE, X 能够使用 REs 的 verbose 状态,使之被组织得更清晰易懂

    I
    IGNORECASE

    使匹配对大小写不敏感;字符类和字符串匹配字母时忽略大小写。举个例子,[A-Z]也可以匹配小写字母,Spam 可以匹配 "Spam", "spam", 或 "spAM"。这个小写字母并不考虑当前位置。

    L
    LOCALE

    影响 \w, \W, \b, 和 \B,这取决于当前的本地化设置。

    locales 是 C 语言库中的一项功能,是用来为需要考虑不同语言的编程提供帮助的。举个例子,如果你正在处理法文文本,你想用 \w+ 来匹配文字,但 \w 只匹配字符类 [A-Za-z];它并不能匹配 "é" 或 "ç"。如果你的系统配置适当且本地化设置为法语,那么内部的 C 函数将告诉程序 "é" 也应该被认为是一个字母。当在编译正则表达式时使用 LOCALE 标志会得到用这些 C 函数来处理 \w 后的编译对象;这会更慢,但也会象你希望的那样可以用 \w+ 来匹配法文文本。

    M
    MULTILINE


    (此时 ^ 和 $ 不会被解释; 它们将在 4.1 节被介绍.)


    使用 "^" 只匹配字符串的开始,而 $ 则只匹配字符串的结尾和直接在换行前(如果有的话)的字符串结尾。当本标志指定后, "^" 匹配字符串的开始和字符串中每行的开始。同样的, $ 元字符匹配字符串结尾和字符串中每行的结尾(直接在每个换行之前)。

    S
    DOTALL

    使 "." 特殊字符完全匹配任何字符,包括换行;没有这个标志, "." 匹配除了换行外的任何字符。

    X
    VERBOSE


    该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。当该标志被指定时,在 RE 字符串中的空白符被忽略,除非该空白符在字符类中或在反斜杠之后;这可以让你更清晰地组织和缩进 RE。它也可以允许你将注释写入 RE,这些注释会被引擎忽略;注释用 "#"号 来标识,不过该符号不能在字符串或反斜杠之后。


    举个例子,这里有一个使用 re.VERBOSE 的 RE;看看读它轻松了多少?

    #!python
    charref = re.compile(r"""&[[]]		   # Start of a numeric entity reference|||here has wrong.i can't fix
    (
    [0-9]+[^0-9]      # Decimal form
    | 0[0-7]+[^0-7]   # Octal form
    | x[0-9a-fA-F]+[^0-9a-fA-F] # Hexadecimal form
    )
    """, re.VERBOSE)
    

    没有 verbose 设置, RE 会看起来象这样:

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

    在上面的例子里,Python 的字符串自动连接可以用来将 RE 分成更小的部分,但它比用 re.VERBOSE 标志时更难懂

    更多模式功能

    到目前为止,我们只展示了正则表达式的一部分功能。在本节,我们将展示一些新的元字符和如何使用组来检索被匹配的文本部分。

    == ==

    更多的元字符

    粗体文字链接标题还有一些我们还没展示的元字符,其中的大部分将在本节展示。


    剩下来要讨论的一部分元字符是零宽界定符(zero-width assertions)。它们并不会使引擎在处理字符串时更快;相反,它们根本就没有对应任何字符,只是简单的成功或失败。举个例子, \b 是一个在单词边界定位当前位置的界定符(assertions),这个位置根本就不会被 \b 改变。这意味着零宽界定符(zero-width assertions)将永远不会被重复,因为如果它们在给定位置匹配一次,那么它们很明显可以被匹配无数次。

    |


    可选项,或者 "or" 操作符。如果 A 和 B 是正则表达式,A|B 将匹配任何匹配了 "A" 或 "B" 的字符串。| 的优先级非常低,是为了当你有多字符串要选择时能适当地运行。Crow|Servo 将匹配"Crow" 或 "Servo", 而不是 "Cro", 一个 "w" 或 一个 "S", 和 "ervo"。


    为了匹配字母 "|",可以用 \|,或将其包含在字符类中,如[|]。

    ^


    匹配行首。除非设置 MULTILINE 标志,它只是匹配字符串的开始。在 MULTILINE 模式里,它也可以直接匹配字符串中的每个换行。


    例如,如果你只希望匹配在行首单词 "From",那么 RE 将用 ^From。

    #!python
    >>> print re.search('^From', 'From Here to Eternity')
    <re.MatchObject instance at 80c1520>
    >>> print re.search('^From', 'Reciting From Memory')
    None
    

    $


    匹配行尾,行尾被定义为要么是字符串尾,要么是一个换行字符后面的任何位置。

    #!python
    >>> print re.search('}$', '{block}')
    <re.MatchObject instance at 80adfa8>
    >>> print re.search('}$', '{block} ')
    None
    >>> print re.search('}$', '{block}\n')
    <re.MatchObject instance at 80adfa8>
    

    匹配一个 "$",使用 \$ 或将其包含在字符类中,如[$]。

    \A


    只匹配字符串首。当不在 MULTILINE 模式,\A 和 ^ 实际上是一样的。然而,在 MULTILINE 模式里它们是不同的;\A 只是匹配字符串首,而 ^ 还可以匹配在换行符之后字符串的任何位置。

    \Z

    Matches only at the end of the string. 
    只匹配字符串尾。

    \b

    单词边界。这是个零宽界定符(zero-width assertions)只用以匹配单词的词首和词尾。单词被定义为一个字母数字序列,因此词尾就是用空白符或非字母数字符来标示的。


    下面的例子只匹配 "class" 整个单词;而当它被包含在其他单词中时不匹配。

    #!python
    >>> p = re.compile(r'\bclass\b')
    >>> print p.search('no class at all')
    <re.MatchObject instance at 80c8f28>
    >>> print p.search('the declassified algorithm')
    None
    >>> print p.search('one subclass is')
    None
    

    当用这个特殊序列时你应该记住这里有两个微妙之处。第一个是 Python 字符串和正则表达式之间最糟的冲突。在 Python 字符串里,"\b" 是反斜杠字符,ASCII值是8。如果你没有使用 raw 字符串时,那么 Python 将会把 "\b" 转换成一个回退符,你的 RE 将无法象你希望的那样匹配它了。下面的例子看起来和我们前面的 RE 一样,但在 RE 字符串前少了一个 "r" 。

    #!python
    >>> p = re.compile('\bclass\b')
    >>> print p.search('no class at all')
    None
    >>> print p.search('\b' + 'class' + '\b')
    <re.MatchObject instance at 80c3ee0>
    

    第二个在字符类中,这个限定符(assertion)不起作用,\b 表示回退符,以便与 Python 字符串兼容。

    \B


    另一个零宽界定符(zero-width assertions),它正好同 \b 相反,只在当前位置不在单词边界时匹配。

    分组

    你经常需要得到比 RE 是否匹配还要多的信息。正则表达式常常用来分析字符串,编写一个 RE 匹配感兴趣的部分并将其分成几个小组。举个例子,一个 RFC-822 的头部用 ":" 隔成一个头部名和一个值,这就可以通过编写一个正则表达式匹配整个头部,用一组匹配头部名,另一组匹配头部值的方式来处理。


    组是通过 "(" 和 ")" 元字符来标识的。 "(" 和 ")" 有很多在数学表达式中相同的意思;它们一起把在它们里面的表达式组成一组。举个例子,你可以用重复限制符,象 *, +, ?, 和 {m,n},来重复组里的内容,比如说(ab)* 将匹配零或更多个重复的 "ab"。

    #!python
    >>> p = re.compile('(ab)*')
    >>> print p.match('ababababab').span()
    (0, 10)
    

    组用 "(" 和 ")" 来指定,并且得到它们匹配文本的开始和结尾索引;这就可以通过一个参数用 group()、start()、end() 和 span() 来进行检索。组是从 0 开始计数的。组 0 总是存在;它就是整个 RE,所以 `MatchObject` 的方法都把组 0 作为它们缺省的参数。稍后我们将看到怎样表达不能得到它们所匹配文本的 span。

    #!python
    >>> p = re.compile('(a)b')
    >>> m = p.match('ab')
    >>> m.group()
    'ab'
    >>> m.group(0)
    'ab'
    

    小组是从左向右计数的,从1开始。组可以被嵌套。计数的数值可以通过从左到右计算打开的括号数来确定。

    #!python
    >>> p = re.compile('(a(b)c)d')
    >>> m = p.match('abcd')
    >>> m.group(0)
    'abcd'
    >>> m.group(1)
    'abc'
    >>> m.group(2)
    'b'
    

    group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。

    #!python
    >>> m.group(2,1,2)
    ('b', 'abc', 'b')
    

    The groups() 方法返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。

    #!python
    >>> m.groups()
    ('abc', 'b')
    

    模式中的逆向引用允许你指定先前捕获组的内容,该组也必须在字符串当前位置被找到。举个例子,如果组 1 的内容能够在当前位置找到的话,\1 就成功否则失败。记住 Python 字符串也是用反斜杠加数据来允许字符串中包含任意字符的,所以当在 RE 中使用逆向引用时确保使用 raw 字符串。


    例如,下面的 RE 在一个字符串中找到成双的词。

    #!python
    >>> p = re.compile(r'(\b\w+)\s+\1')
    >>> p.search('Paris in the the spring').group()
    'the the'
    

    象这样只是搜索一个字符串的逆向引用并不常见 -- 用这种方式重复数据的文本格式并不多见 -- 但你不久就可以发现它们用在字符串替换上非常有用。

    无捕获组和命名组

    精心设计的 REs 也许会用很多组,既可以捕获感兴趣的子串,又可以分组和结构化 RE 本身。在复杂的 REs 里,追踪组号变得困难。有两个功能可以对这个问题有所帮助。它们也都使用正则表达式扩展的通用语法,因此我们来看看第一个。


    Perl 5 对标准正则表达式增加了几个附加功能,Python 的 re 模块也支持其中的大部分。选择一个新的单按键元字符或一个以 "\" 开始的特殊序列来表示新的功能,而又不会使 Perl 正则表达式与标准正则表达式产生混乱是有难度的。如果你选择 "&" 做为新的元字符,举个例子,老的表达式认为 "&" 是一个正常的字符,而不会在使用 \& 或 [&] 时也不会转义。


    Perl 开发人员的解决方法是使用 (?...) 来做为扩展语法。"?" 在括号后面会直接导致一个语法错误,因为 "?" 没有任何字符可以重复,因此它不会产生任何兼容问题。紧随 "?" 之后的字符指出扩展的用途,因此 (?=foo)


    Python 新增了一个扩展语法到 Perl 扩展语法中。如果在问号后的第一个字符是 "P",你就可以知道它是针对 Python 的扩展。目前有两个这样的扩展: (?P<name>...) 定义一个命名组,(?P=name) 则是对命名组的逆向引用。如果 Perl 5 的未来版本使用不同的语法增加了相同的功能,那么 re 模块也将改变以支持新的语法,与此同时为了兼容性的目的而继续保持的 Python 专用语法。


    现在我们看一下普通的扩展语法,我们回过头来简化在复杂 REs 中使用组运行的特性。因为组是从左到右编号的,而且一个复杂的表达式也许会使用许多组,它可以使跟踪当前组号变得困难,而修改如此复杂的 RE 是十分麻烦的。在开始时插入一个新组,你可以改变它之后的每个组号。


    首先,有时你想用一个组去收集正则表达式的一部分,但又对组的内容不感兴趣。你可以用一个无捕获组: (?:...) 来实现这项功能,这样你可以在括号中发送任何其他正则表达式。

    #!python
    >>> m = re.match("([abc])+", "abc")
    >>> m.groups()
    ('c',)
    >>> m = re.match("(?:[abc])+", "abc")
    >>> m.groups()
    ()
    

    除了捕获匹配组的内容之外,无捕获组与捕获组表现完全一样;你可以在其中放置任何字符,可以用重复元字符如 "*" 来重复它,可以在其他组(无捕获组与捕获组)中嵌套它。(?:...) 对于修改已有组尤其有用,因为你可以不用改变所有其他组号的情况下添加一个新组。捕获组和无捕获组在搜索效率方面也没什么不同,没有哪一个比另一个更快。


    其次,更重要和强大的是命名组;与用数字指定组不同的是,它可以用名字来指定。


    命令组的语法是 Python 专用扩展之一: (?P<name>...)。名字很明显是组的名字。除了该组有个名字之外,命名组也同捕获组是相同的。`MatchObject` 的方法处理捕获组时接受的要么是表示组号的整数,要么是包含组名的字符串。命名组也可以是数字,所以你可以通过两种方式来得到一个组的信息:

    #!python
    >>> p = re.compile(r'(?P<word>\b\w+\b)')
    >>> m = p.search( '(((( Lots of punctuation )))' )
    >>> m.group('word')
    'Lots'
    >>> m.group(1)
    'Lots'
    

    命名组是便于使用的,因为它可以让你使用容易记住的名字来代替不得不记住的数字。这里有一个来自 imaplib 模块的 RE 示例:

    #!python
    InternalDate = re.compile(r'INTERNALDATE "'
    r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
    	r'(?P<year>[0-9][0-9][0-9][0-9])'
    r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
    r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
    r'"')
    

    很明显,得到 m.group('zonem') 要比记住得到组 9 要容易得多。


    因为逆向引用的语法,象 (...)\1 这样的表达式所表示的是组号,这时用组名代替组号自然会有差别。还有一个 Python 扩展:(?P=name) ,它可以使叫 name 的组内容再次在当前位置发现。正则表达式为了找到重复的单词,(\b\w+)\s+\1 也可以被写成 (?P<word>\b\w+)\s+(?P=word):

    #!python
    >>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
    >>> p.search('Paris in the the spring').group()
    'the the'
    

    前向界定符

    另一个零宽界定符(zero-width assertion)是前向界定符。前向界定符包括前向肯定界定符和前项否定界定符,如下所示:

    (?=...)

    前向肯定界定符。如果所含正则表达式,以 ... 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。

    (?!...)

    前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功


    通过示范在哪前向可以成功有助于具体实现。考虑一个简单的模式用于匹配一个文件名,并将其通过 "." 分成基本名和扩展名两部分。如在 "news.rc" 中,"news" 是基本名,"rc" 是文件的扩展名。


    匹配模式非常简单:

    .*[.].*$
    

    注意 "." 需要特殊对待,因为它是一个元字符;我把它放在一个字符类中。另外注意后面的 $; 添加这个是为了确保字符串所有的剩余部分必须被包含在扩展名中。这个正则表达式匹配 "foo.bar"、"autoexec.bat"、 "sendmail.cf" 和 "printers.conf"。


    现在,考虑把问题变得复杂点;如果你想匹配的扩展名不是 "bat" 的文件名?一些不正确的尝试:

    .*[.][^b].*$
    

    上面的第一次去除 "bat" 的尝试是要求扩展名的第一个字符不是 "b"。这是错误的,因为该模式也不能匹配 "foo.bar"。

    .*[.]([^b]..|.[^a].|..[^t])$
    

    当你试着修补第一个解决方法而要求匹配下列情况之一时表达式更乱了:扩展名的第一个字符不是 "b"; 第二个字符不是 "a";或第三个字符不是 "t"。这样可以接受 "foo.bar" 而拒绝 "autoexec.bat",但这要求只能是三个字符的扩展名而不接受两个字符的扩展名如 "sendmail.cf"。我们将在努力修补它时再次把该模式变得复杂。

    .*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
    

    在第三次尝试中,第二和第三个字母都变成可选,为的是允许匹配比三个字符更短的扩展名,如 "sendmail.cf"。


    该模式现在变得非常复杂,这使它很难读懂。更糟的是,如果问题变化了,你想扩展名不是 "bat" 和 "exe",该模式甚至会变得更复杂和混乱。


    前向否定把所有这些裁剪成:

    .*[.](?!bat$).*$
    

    前向的意思:如果表达式 bat 在这里没有匹配,尝试模式的其余部分;如果 bat$ 匹配,整个模式将失败。后面的 $ 被要求是为了确保象 "sample.batch" 这样扩展名以 "bat" 开头的会被允许。


    将另一个文件扩展名排除在外现在也容易;简单地将其做为可选项放在界定符中。下面的这个模式将以 "bat" 或 "exe" 结尾的文件名排除在外。

    .*[.](?!bat$|exe$).*$
    

    修改字符串

    到目前为止,我们简单地搜索了一个静态字符串。正则表达式通常也用不同的方式,通过下面的 `RegexObject` 方法,来修改字符串。

    方法/属性 作用
    split() 将字符串在 RE 匹配的地方分片并生成一个列表,
    sub() 找到 RE 匹配的所有子串,并将其用一个不同的字符串替换
    subn() 与 sub() 相同,但返回新的字符串和替换次数

    将字符串分片

    `RegexObject` 的 split() 方法在 RE 匹配的地方将字符串分片,将返回列表。它同字符串的 split() 方法相似但提供更多的定界符;split()只支持空白符和固定字符串。就象你预料的那样,也有一个模块级的 re.split() 函数。

    split(string [, maxsplit = 0])
    

    通过正则表达式将字符串分片。如果捕获括号在 RE 中使用,那么它们的内容也会作为结果列表的一部分返回。如果 maxsplit 非零,那么最多只能分出 maxsplit 个分片。


    你可以通过设置 maxsplit 值来限制分片数。当 maxsplit 非零时,最多只能有 maxsplit 个分片,字符串的其余部分被做为列表的最后部分返回。在下面的例子中,定界符可以是非数字字母字符的任意序列。

    #!python
    >>> p = re.compile(r'\W+')
    >>> p.split('This is a test, short and sweet, of split().')
    ['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
    >>> p.split('This is a test, short and sweet, of split().', 3)
    ['This', 'is', 'a', 'test, short and sweet, of split().']
    

    有时,你不仅对定界符之间的文本感兴趣,也需要知道定界符是什么。如果捕获括号在 RE 中使用,那么它们的值也会当作列表的一部分返回。比较下面的调用:

    #!python
    >>> p = re.compile(r'\W+')
    >>> p2 = re.compile(r'(\W+)')
    >>> p.split('This... is a test.')
    ['This', 'is', 'a', 'test', '']
    >>> p2.split('This... is a test.')
    ['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']
    

    模块级函数 re.split() 将 RE 作为第一个参数,其他一样。

    #!python
    >>> re.split('[\W]+', 'Words, words, words.')
    ['Words', 'words', 'words', '']
    >>> re.split('([\W]+)', 'Words, words, words.')
    ['Words', ', ', 'words', ', ', 'words', '.', '']
    >>> re.split('[\W]+', 'Words, words, words.', 1)
    ['Words', 'words, words.']
    

    搜索和替换

    其他常见的用途就是找到所有模式匹配的字符串并用不同的字符串来替换它们。sub() 方法提供一个替换值,可以是字符串或一个函数,和一个要被处理的字符串。

    sub(replacement, string[, count = 0])
    

    返回的字符串是在字符串中用 RE 最左边不重复的匹配来替换。如果模式没有发现,字符将被没有改变地返回。


    可选参数 count 是模式匹配后替换的最大次数;count 必须是非负整数。缺省值是 0 表示替换所有的匹配。


    这里有个使用 sub() 方法的简单例子。它用单词 "colour" 替换颜色名。

    #!python
    >>> p = re.compile( '(blue|white|red)')
    >>> p.sub( 'colour', 'blue socks and red shoes')
    'colour socks and colour shoes'
    >>> p.sub( 'colour', 'blue socks and red shoes', count=1)
    'colour socks and red shoes'
    

    subn() 方法作用一样,但返回的是包含新字符串和替换执行次数的两元组。

    #!python
    >>> p = re.compile( '(blue|white|red)')
    >>> p.subn( 'colour', 'blue socks and red shoes')
    ('colour socks and colour shoes', 2)
    >>> p.subn( 'colour', 'no colours at all')
    ('no colours at all', 0)
    

    空匹配只有在它们没有紧挨着前一个匹配时才会被替换掉。

    #!python
    >>> p = re.compile('x*')
    >>> p.sub('-', 'abxd')
    '-a-b-d-'
    

    如果替换的是一个字符串,任何在其中的反斜杠都会被处理。"\n" 将会被转换成一个换行符,"\r"转换成回车等等。未知的转义如 "\j" 则保持原样。逆向引用,如 "\6",被 RE 中相应的组匹配而被子串替换。这使你可以在替换后的字符串中插入原始文本的一部分。


    这个例子匹配被 "{" 和 "}" 括起来的单词 "section",并将 "section" 替换成 "subsection"。

    #!python
    >>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE)
    >>> p.sub(r'subsection{\1}','section{First} section{second}')
    'subsection{First} subsection{second}'
    

    还可以指定用 (?P<name>...) 语法定义的命名组。"\g<name>" 将通过组名 "name" 用子串来匹配,并且 "\g<number>" 使用相应的组号。所以 "\g<2>" 等于 "\2",但能在替换字符串里含义不清,如 "\g<2>0"。("\20" 被解释成对组 20 的引用,而不是对后面跟着一个字母 "0" 的组 2 的引用。)

    #!python
    >>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE)
    >>> p.sub(r'subsection{\1}','section{First}')
    'subsection{First}'
    >>> p.sub(r'subsection{\g<1>}','section{First}')
    'subsection{First}'
    >>> p.sub(r'subsection{\g<name>}','section{First}')
    'subsection{First}'
    

    替换也可以是一个甚至给你更多控制的函数。如果替换是个函数,该函数将会被模式中每一个不重复的匹配所调用。在每次调用时,函数会被传入一个 `MatchObject` 的对象作为参数,因此可以用这个对象去计算出替换字符串并返回它。


    在下面的例子里,替换函数将十进制翻译成十六进制:

    #!python
    >>> def hexrepl( match ):
    ...     "Return the hex string for a decimal number"
    ...     value = int( match.group() )
    ...     return hex(value)
    ...
    >>> p = re.compile(r'\d+')
    >>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')
    'Call 0xffd2 for printing, 0xc000 for user code.'
    

    当使用模块级的 re.sub() 函数时,模式作为第一个参数。模式也许是一个字符串或一个 `RegexObject`;如果你需要指定正则表达式标志,你必须要么使用 `RegexObject` 做第一个参数,或用使用模式内嵌修正器,如 sub("(?i)b+", "x", "bbbb BBBB") returns 'x x'。

    常见问题

    正则表达式对一些应用程序来说是一个强大的工具,但在有些时候它并不直观而且有时它们不按你期望的运行。本节将指出一些最容易犯的常见错误。

    使用字符串方式

    有时使用 re 模块是个错误。如果你匹配一个固定的字符串或单个的字符类,并且你没有使用 re 的任何象 IGNORECASE 标志的功能,那么就没有必要使用正则表达式了。字符串有一些方法是对固定字符串进行操作的,它们通常快很多,因为它们都是一个个经过优化的 C 小循环,用以代替大的、更具通用性的正则表达式引擎。


    举个 用一个固定字符串替换另一个 的例子,如:你可以把 "deed" 替换成 "word"。re.sub() 似乎正是胜任这个工作的函数,但还是考虑考虑 replace() 方法吧。注意 replace() 也可以在单词里面进行替换,可以把 "swordfish" 变成 "sdeedfish"。不过 RE 也是可以做到的。(为了避免替换单词的一部分,模式将写成 \bword\b,这是为了要求 "word" 两边有一个单词边界。这是个超出 replace 能力的工作)。


    另一个常见任务是从一个字符串中删除单个字符或用另一个字符来替代它。你也许可以用 re.sub('\n',' ', s) 这样来实现,但 translate() 能够实现这两个任务,而且比任何正则表达式操作起来更快。 (translate 需要配合 string.maketrans 使用。例如:import string 后 'a1b3'.translate(string.maketrans('ab', 'cd')) )


    总之,在使用 re 模块之前,先考虑一下你的问题是否可以用更快、更简单的字符串方法来解决。

    match() vs search()

    match() 函数只检查 RE 是否在字符串开始处匹配,而 search() 则是扫描整个字符串。记住这一区别是重要的。记住,match() 只报告一次成功的匹配,它将从 0 处开始;如果匹配不是从 0 开始的,match() 将不会报告它。

    #!python
    >>> print re.match('super', 'superstition').span()
    (0, 5)
    >>> print re.match('super', 'insuperable')
    None
    

    另一方面,search() 将扫描整个字符串,并报告它找到的第一个匹配。

    #!python
    >>> print re.search('super', 'superstition').span()
    (0, 5)
    >>> print re.search('super', 'insuperable').span()
    (2, 7)
    

    有时你可能倾向于使用 re.match(),只在RE的前面部分添加 .* 。请尽量不要这么做,最好采用 re.search() 代替之。正则表达式编译器会对 REs 做一些分析以便可以在查找匹配时提高处理速度。一个那样的分析机会指出匹配的第一个字符是什么;举个例子,模式 Crow 必须从 "C" 开始匹配。分析机可以让引擎快速扫描字符串以找到开始字符,并只在 "C" 被发现后才开始全部匹配。

    添加 .* 会使这个优化失败,这就要扫描到字符串尾部,然后回溯以找到 RE 剩余部分的匹配。使用 re.search() 代替。

    贪婪 vs 不贪婪

    当重复一个正则表达式时,如用 a*,操作结果是尽可能多地匹配模式。当你试着匹配一对对称的定界符,如 HTML 标志中的尖括号时这个事实经常困扰你。匹配单个 HTML 标志的模式不能正常工作,因为 .* 的本质是“贪婪”的

    #!python
    >>> s = '<html><head><title>Title</title>'
    >>> len(s)
    32
    >>> print re.match('<.*>', s).span()
    (0, 32)
    >>> print re.match('<.*>', s).group()
    <html><head><title>Title</title>
    

    RE 匹配 在 "<html>" 中的 "<",.* 消耗掉字符串的剩余部分。在 RE 中保持更多的左,虽然 > 不能匹配在字符串结尾,因此正则表达式必须一个字符一个字符地回溯,直到它找到 > 的匹配。最终的匹配从 "<html" 中的 "<" 到 "</title>" 中的 ">",这并不是你所想要的结果。


    在这种情况下,解决方案是使用不贪婪的限定符 *?、+?、?? 或 {m,n}?,尽可能匹配小的文本。在上面的例子里, ">" 在第一个 "<" 之后被立即尝试,当它失败时,引擎一次增加一个字符,并在每步重试 ">"。这个处理将得到正确的结果:

    #!python
    >>> print re.match('<.*?>', s).group()
    <html>
    

    注意用正则表达式分析 HTML 或 XML 是痛苦的。变化混乱的模式将处理常见情况,但 HTML 和 XML 则是明显会打破正则表达式的特殊情况;当你编写一个正则表达式去处理所有可能的情况时,模式将变得非常复杂。象这样的任务用 HTML 或 XML 解析器。

    粗体文字粗体文字'


    不用 re.VERBOSE

    现在你可能注意到正则表达式的表示是十分紧凑,但它们非常不好读。中度复杂的 REs 可以变成反斜杠、圆括号和元字符的长长集合,以致于使它们很难读懂。


    在这些 REs 中,当编译正则表达式时指定 re.VERBOSE 标志是有帮助的,因为它允许你可以编辑正则表达式的格式使之更清楚。


    re.VERBOSE 标志有这么几个作用。在正则表达式中不在字符类中的空白符被忽略。这就意味着象 dog | cat 这样的表达式和可读性差的 dog|cat 相同,但 [a b] 将匹配字符 "a"、"b" 或 空格。另外,你也可以把注释放到 RE 中;注释是从 "#" 到下一行。当使用三引号字符串时,可以使 REs 格式更加干净:

    #!python
    pat = re.compile(r"""
    \s*                 # Skip leading whitespace
    (?P<header>[^:]+)   # Header name
    \s* :               # Whitespace, and a colon
    (?P<value>.*?)      # The header's value -- *? used to
    # lose the following trailing whitespace
    \s*$                # Trailing whitespace to end-of-line
    """, re.VERBOSE)
    

    这个要难读得多:

    #!python
    pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")
    

    反馈

    正则表达式是一个复杂的主题。本文能否有助于你理解呢?哪些部分是否不清晰,或在这儿没有找到你所遇到的问题?如果是那样的话,请将建议发给作者以便改进。

    描述正则表达式最全面的书非Jeffrey Friedl 写的《精通正则表达式》莫属,该书由O'Reilly 出版。可惜该书只专注于 Perl 和 Java 风格的正则表达式,不含任何 Python 材料,所以不足以用作Python编程时的参考。(第一版包含有 Python 现已过时的 regex 模块,自然用处不大)。

    《精通正则表达式》第三版已经有部分正则表达式使用python说明,另外PHP风格的更是独立一个章节说明。--why?

    大标题文字

    关于本文档

    本文档使用 LaTeX2HTML 转换器生成。

    LaTeX2HTML is Copyright © 1993, 1994, 1995, 1996, 1997, Nikos Drakos, Computer Based Learning Unit, University of Leeds, and Copyright © 1997, 1998, Ross Moore, Mathematics Department, Macquarie University, Sydney.

    The application

    of LaTeX2HTML to the Python documentation

    [[Media:Media:Example.ogg[[Media:Example.ogg]]]]has been heavily tailored by Fred L. Drake, Jr. Original navigation icons were contributed by Christopher Petrilli.ssssssssssssssssss
    

    问候语?@#

    展开全文
  • Python 正则表达式详解

    2018-06-28 15:10:20
    正则表达式 正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。 我们判断一...

    正则表达式

        正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。

        我们判断一个字符串是否符合规则的方法是:
            创建一个用于匹配的正则表达式
            用该正则表达式去匹配字符串判断是否合法。

        因为正则表达式也是用字符串表示的,所以,我们要首先了解如何用字符来描述字符。

    特别字符描述
    [ ]

    [amk] will match 'a', 'm', or 'k'.

    [a-z]   [0-9A-Fa-f] 表示范围匹配,一个[]内有多个范围时只会从某一个范围中匹配。[0-5][0-9]

     (+*) 在[]中失去特殊含义  [(+*)] 将会匹配'(', '+', '*', or ')'.

    \w or \S等在[] 可以使用

    如果[]内的第一个字符是^,那么表示except。[^5] will match any character except '5'。如果不是第一个字符那么^失去特殊含义

    ( )标记一个子表达式(Group)的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 \( 和 \)。
    *匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。
    +匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。
    .

    匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。标志re.DOTALL表示.可以匹配\n

     


    ?匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。
    \将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, 'n' 匹配字符 'n'。'\n' 匹配换行符。序列 '\\' 匹配 "\",而 '\(' 则匹配 "("。
    ^匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。RE.
    $匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 '\n' 或 '\r'。要匹配 $ 字符本身,请使用 \$。
    |

    指明两项之间的一个选择。要匹配 |,请使用 \|。

    可以分隔任意多个re,例如  A|B|C|D 表示四个任意RE中间选一个, 顺序从左到右


        由于Python的字符串本身也用\转义,因此我们强烈建议使用Python的r前缀,就不用考虑转义的问题了:例如:如果我们相想匹配一个'\'字符,那么对应的re pattern是'\\',对应的re str就是'\\\\',相当于需要转义两次,而使用了r前缀的话,对应的re str就是r'\\',不用考虑第一次字符串转义。

    字符描述
    *匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。
    +匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
    ?匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 、 "does" 中的 "does" 、 "doxy" 中的 "do" 。? 等价于 {0,1}。
    {n}n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。
    {n,}n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。
    {n,m}

    m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。


    字符描述
    (?P<name>...) 
    (pattern)

    匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 '\(' 或 '\)'。

    (?:pattern)

    匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。

    (?=pattern)

    正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,"Windows(?=95|98|NT|2000)"能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

    (?!pattern)

    正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如"Windows(?!95|98|NT|2000)"能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

    (?<=pattern)反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,"(?<=95|98|NT|2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。
    (?<!pattern)反向否定预查,与正向否定预查类似,只是方向相反。例如"(?<!95|98|NT|2000)Windows"能匹配"3.1Windows"中的"Windows",但不能匹配"2000Windows"中的"Windows"。
    (?#...) 注释

    \w匹配字母数字及下划线  '[A-Za-z0-9_]'。
    \W匹配非字母数字及下划线, 等价于 '[^A-Za-z0-9_]'。
    \s匹配任意空白字符,等价于 [ \f\n\r\t\v]
    \S匹配任意非空字符, [^ \f\n\r\t\v]
    \d匹配任意数字,等价于 [0-9].
    \D匹配任意非数字
    \A匹配字符串开始
    \Z匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
    \z匹配字符串结束
    \G匹配最后匹配完成的位置。
    \b匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
    \B匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
    \n, \t, 等.匹配一个换行符。匹配一个制表符。等
    \1...\9匹配第n个分组的内容。
    \10匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。

    \d匹配一个数字字符。等价于 [0-9]。
    \D匹配一个非数字字符。等价于 [^0-9]。
    \s匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
    \S匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
    \w匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'。
    \W匹配任何非单词字符。等价于 '[^A-Za-z0-9_]'。

       贪婪匹配与非贪婪匹配

        需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0:

    >>> re.match(r'^(\d+)(0*)$', '102300').groups()
    ('102300', '')
        由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了。
        必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配: 
    >>> re.match(r'^(\d+?)(0*)$', '102300').groups()
    ('1023', '00')

        同理,?还可以用在其他地方,例如

        *?, +?, ??

             For example, <.*> is matched against '<a> b <c>'  but  <.*?> will match only '<a>'

        {m,n}?

            For example, on the 6-character string 'aaaaaa', a{3,5} will match 5 'a' characters, while a{3,5}? will only match 3 characters.



         


        https://docs.python.org/3/library/re.html

    展开全文
  • Python正则表达式详解

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,584
精华内容 633
关键字:

python正则表达式详解

python 订阅