美文网首页
Python正则表达式

Python正则表达式

作者: 东旭曦影 | 来源:发表于2020-03-05 23:33 被阅读0次

    为学习Python中正则表达式的用法,通读了["Regular Expression HOWTO"]: https://docs.python.org/3.7/howto/regex.html#regex-howto
    在此,将原文的要点进行记录与分享。


    引用Introduction

    Python中的正则表达式(Regular expressions,RE)模块为re,其底层匹配引擎由C编写。正则表达式语言相对较小而且严格受限制,并不能应对所有情况。有时应该直接使用Python编写合适的代码进行处理,尽管Python代码处理效率可能比精细的正则表达式处理效率更低,但Python代码更加直接明确。


    基本模式Simple Patterns

    元字符

    所有正则表达式的元字符

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

    方括号[]的组合用以指定一组字符集[asdf]

    • 连字符-简化标识字符范围。[a-z]匹配所有小写26个字母。
    • 在方括号中的元字符不起元字符功能,直接表示字符。[akm$]匹配akw$4个字符。
    • 方括号中的首个字符为^时,表示反义,用以匹配不在字符集的字符。[^5]匹配所有不是'5'的字符,但[5^]匹配字符5^

    反斜杠\是正则表达式的转义字符,其后跟随不同字符将有不同含义。例如,\w[a-zA-Z0-9_]表示相同含义,其它转义遇到时再说明。

    • \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_]
    • 上述序列可以用在[ ]中,表示相应的字符集

    句点.匹配任意非换行的字符(在re.DOTALL设置中有不同含义)

    匹配重复的字符串

    星号*表示重复前面的字符0次或任意次。
    加号+表示重复前面的字符至少1次任意次。
    问号?表示重复前面的字符0次或1次。
    花括号{m,n}表示重复前面的字符至少m次,至多n次。省略参数m表示至少0次,省略n表示匹配尽可能多次。
    上述所有重复模式元字符均为贪婪匹配,即匹配尽可能多的字符。


    使用正则表达式Using Regular Expresssions

    python正则表达式匹配模式对象

    Python模块函数re.compile()将正则表达式RE编译为相应的匹配对象,正则表达式RE以字符串形式输入。

    >>> import re
    >>> p = re.compile('ab*')
    >>> p
    re.compile('ab*')
    

    此外re.compile()包含一些可选标识,例如

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

    python正则表达式的反斜杠灾难

    考虑正则表达式匹配字符串\section的情况。在正则表达语法中,为匹配字符反斜杠\,需要使用额外反斜杠进行转义表达,即正则表达式应写作'\section'。并且,在python的字符串语法中,反斜杠\同样为转义字符,为表示上述正则表达式,相应的python字符串为'\\\\section'。这就导致了反斜杠灾难问题。
    不过,带有前缀r的python裸字符串不进行转义处理,即上述正则表达式可写作r'\\section'。因此在python的正则表达式中,通常使用r前缀的裸字符串。

    进行匹配

    在准备后RE匹配模式对象后,可以进行如下匹配操作:

    方法/属性 说明
    match() 判断RE是否匹配字符串开始部分
    search() 查找字符串是否与RE匹配的部分
    findall() 将所有与RE匹配的子字符串以list返回
    finditer() 将所有与RE匹配的子字符串以iterator返回

    方法match()search()返回的对象为RE匹配对象(re.Match),未成功匹配时返回None

    >>> import re
    >>> p = re.compile('[a-z]+')
    >>> p
    re.compile('[a-z]+')
    
    >>> p.match("")
    >>> print(p.match(""))
    None
    
    >>> m = p.match('tempo')
    >>> m
    <re.Match object; span=(0, 5), match='tempo'>
    

    RE匹配对象具有如下方法

    方法/属性 说明
    group() 返回RE匹配的字符串
    start() 返回RE匹配的字符串起始位置
    end() 返回RE匹配的字符串结束位置
    span() 返回一个元组包含上述字符串的起始、结束位置
    >>> print(p.match('::: message'))
    None
    >>> m = p.search('::: message'); print(m)
    <re.Match object; span=(4, 11), match='message'>
    >>> m.group()
    'message'
    >>> m.span()
    (4, 11)
    

    模块层级的函数

    上述macht(), serach(), findall()等函数并非必须通过RE模式对象进行方法调用,模块re提供的直接使用的方法,以RE与待匹配的字符串作为输入,例如

    >>> print(re.match(r'From\s+', 'Fromage amk'))
    None
    >>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')  
    <re.Match object; span=(0, 5), match='From '>
    

    RE编译模式标志位

    标志Flag 含义
    ASCII, A 使多个转义字符集\w,\b\s\d仅匹配ASCII字符集(相对于UNICODE)
    DOTALL, S 使元字符句点.匹配包括换行符的所有字符
    IGNORECAE, I 忽略字符大小写区别
    LOCALE, L 以本地字符串进行匹配
    MULTILINE, M 对字符串以多行模式匹配,影响元字符^$的含义
    VERBOSE, X 启用verbose REs,可优化RE表达式显示

    其中VERBOSE模式的RE忽略空格与换行,且#表示行注释,例如

    # Use verbose setting
    charref = re.compile(r"""
     &[#]                # Start of a numeric entity reference
     (
         0[0-7]+         # Octal form
       | [0-9]+          # Decimal form
       | x[0-9a-fA-F]+   # Hexadecimal form
     )
     ;                   # Trailing semicolon
    """, re.VERBOSE)
    
    # Without verbose setting
    charref = re.compile("&#(0[0-7]+"
                         "|[0-9]+"
                         "|x[0-9a-fA-F]+);")
    

    高阶模式More Pattern Power

    元字符

    下面将讨论的元字符包括

    | ^ $ \A \Z \b \B

    这些元字符都是零宽断言,即指定特殊含义但并不消耗任何字符串字符

    • 竖线|表示或者,例如AB为RE,那么A|B匹配A或者B
    • ^匹配行的开始,但在MULTILINE标志下仅匹配整个字符串的开始
    • $匹配行的末位,但在MULTILINE标志下仅匹配整个字符串的末尾
    • \A匹配整个字符串的开始
    • \Z匹配整个字符串的末尾
    • \b表示单词的边界,匹配一个单词的开始或结束
    • \B\b的含义相反,不能是单词的结束或开始

    例如\b的用法

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

    RE中的分组

    分组由RE中的()确定,有以下几方面作用

    • 分组作为一个整体参与RE表达式匹配
    • 分组指定的子字符串可以从匹配结果中获取相关信息
    • RE表达式中可以引用前面使用的分组以简化表达式

    与数学计算中括号有相似的含义,()将其中的表达式部分作为一个分组,例如当分组的后续为*+等则整个分组进行重复。例如

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

    ()指定的分组代表匹配结果的子字符串,可由group()获得,序号0指代整个RE的匹配字符串,从序号1开始依次指代各个子字符串,顺序与(在RE表达式中的顺序一致。

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

    RE表达式可以引用前面由()指定的分组,例如\1表达式中第1个分组的内容

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

    在RE表达式中,括号(紧跟?时,?没有任何字符用于重复,这本是一种语法错误,但RE由此扩展定义一批特殊含义的表达式

    扩展表达式定义 含义
    (?:...) 不需捕获分组信息的分组
    (?P<name>...) 命名的分组,Python语言特性
    (?=...) 零宽的超前断言分组
    (?!...) 零宽的超前否定断言分组

    不捕获的分组

    在一个复杂的RE表达式中,有时仅需要分组作为整体参与运算,但却不需要在结果中获取该分组信息,这时使用不捕获的分组表示(?:...)。不捕获的分组不在子字符串中占有序号,因此不影响其它分组。

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

    python扩展命名分组

    Python语言定义了命名的分组,与序号的分组作用一致。还可以使用(?P=name)指代前面的命名分组,与\1作用类似。

    >>> p = re.compile(r'(?P<word>\b\w+\b)')
    >>> m = p.search( '(((( Lots of punctuation )))' )
    >>> m.group('word')
    'Lots'
    >>> m.group(1)
    'Lots'
    
    >>> p = re.compile(r'\b(?P<word>\w+)\s+(?P=word)\b')
    >>> p.search('Paris in the the spring').group()
    'the the'
    

    超前断言分组

    (?=...)是一个零宽断言,若后续字符串与...部分匹配则匹配断言成功,否则匹配失败。与普通的匹配不同点在于(?=...)不消耗字符串内容仅作为尝试查看

    (?!...)(?=...)反义,若后续字符串与...部分不匹配时则匹配断言称,否则匹配失败。

    举例,若希望对含有.分隔的文件名(例如foo.py)匹配,RE表达式为

    .*[.].*$

    此外,若不想匹配bat文件,那么不使用超前断言的RE表达式可以写为

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

    若同时不想匹配batexe文件,那么不使用超前断言的RE表达式将十分复杂,而使用超前断言可以写作

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


    字符串编辑Modifying Strings

    正则表达式可在多方面用于字符串编辑

    方法/属性 功能
    split() 根据RE匹配分割字符串
    sub() 将所有与RE匹配的子字符串进行替换
    subn() 与sub()相似,额外返回替换的子字符串数量

    分割字符串

    .split(string[, maxsplit=0])参数maxsplit可以指定最大的分割次数,另外在RE表达式中使用了分组括号,则分组的内容也会出现在结果中

    >>> 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', '.', '']
    

    查找与替换

    .sub(replacement, string[, count=0])从字符串最左侧开始依次替换所有不相互重叠的子字符串。若RE模式中有*将匹配所有空子字符串,此时的每次替换仅进行一次。

    >>> p = re.compile('(blue|white|red)')
    >>> p.sub('colour', 'blue socks and red shoes')
    'colour socks and colour shoes'
    
    >>> p = re.compile('x*')
    >>> p.sub('-', 'abxd')
    '-a-b--d-'
    

    在替换字符串replacement中,可以引用RE表达式匹配的分组,例如下面的例子中\1分别指代了原字符串中的'First''second'

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

    替换字符串的一个额外的语法是\g<1>指代RE表达式中的分组1,作用与\1相同。不过采用后者时可能会出现模糊歧义(例如当替换字符串中出现\10时,可以表示分组10,也可以表示分组1与字符'0')对于Python,可以在替换字符串中使用\g<name>指代RE表达式中的命名分组。

    >>> 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}'
    

    参数replacement还可以为函数,接受匹配结果的match object对象,返回字符串

    >>> 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.'
    
    

    公共问题Common Problems

    优先考虑字符串的内部方法

    有时,仅需要将源字符串中的'foo'替换为'bar',那么字符串的replace()方法更合适,而不是re.sub()。不过,如果需要替换的目标包括'foo''Foo'等大小写不定情况,或者位于其它长单词之间'ffooo'(将被replace()替换为'fbaro'),此时应使用re.sub()

    另一种常见的情况是将源字符串中的某些单个字符替换为其它字符,例如re.sub('\n', ' ', S),此时字符串的translate()方法更合适。

    match()与search()

    有时在re.match()的RE表达式中添加.*前缀以实现re.search()的功能,此时直接使用re.search()的效率更高。

    贪婪与非贪婪

    在表重复匹配的元字符后跟?将采取非贪婪匹配的重复模式,如*?+???{m,n}?这些将匹配尽量少次的重复模式。

    >>> s = '<html><head><title>Title</title>'
    >>> print(re.match('<.*>', s).group())
    <html><head><title>Title</title>
    >>> print(re.match('<.*?>', s).group())
    <html>
    

    不过,对HTML或者XML使用RE处理是很头疼的,直接使用Python相应的处理模块更好。

    本文内容主要翻译自["Regular Expression HOWTO"]: https://docs.python.org/3.7/howto/regex.html#regex-howto 若侵权请联系删除

    相关文章

      网友评论

          本文标题:Python正则表达式

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