添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

正则表达式(regular expression) ,用于字符串匹配、替换,是字符串的重要处理方式。其中 正则 是有规则,有规律的意思。正则表达式有 三要素 ,本文主要介绍 Python 中的这三要素:

  • 要处理的字符(串),需要注意转义序列
  • 用于处理的程序,即正则引擎:Python 自带 re 模块
  • 处理规则:正则表达式语法
  • 1 Python 字符串

    1.1 转义序列

    Python 字符串可以包含两部分:

  • 普通字符组成的字符串
  • 转义序列组成的字符串
  • 转义序列(Escape Squence) 转义字符(Escape Character) 和后续字符组成,转义字符放在字符序列前面时,它将对它后续的几个字符进行替代并解释。转义字符是 元字符(Meta Character) 的一种特殊情况。Python 中转义字符是反斜杠( \ )。Python 中所有转义序列如下表:

    后面的描述中,转义字符特指转义序列。

    处理引号也可以用转义字符:Python 并不明确区分单双引号,只要要匹配使用就行;如要表示一个字面意义上的引号,需用另外一种引号来嵌套,或者转义字符:

    "'"  # use another kind of qoute
    "\""  # use escape character
    

    输入这些符号时需要多次敲键盘,但 Python 都把它们当成长度为1的字符来处理:

    In [1]: len("\017")
    Out[1]: 1
    In [2]: len("\n")
    Out[2]: 1
    In [3]: len("\\")
    Out[3]: 1
    

    1.2 Python 中转义序列标志符

    反斜杠是 Python 中转义序列标志符,它有如下特性需要注意:

  • 表示一个字面意义的\需要转义序列标志符\
  • Python 中显示一个字面意义上\则是\\
  • In [1]: len("")
    Out[1]: 0
    In [2]: len("\")
      File "<ipython-input-2-553b6950c0a1>", line 1
        len("\")
    SyntaxError: EOL while scanning string literal
    In [3]: len("\\")
    Out[3]: 1
    In [4]: "\\"[0]
    Out[4]: "\\"
    

    转义序列标志只有在特定的字符组合前才有效果,如果后面的字符组合不能组合为转义,则只会被当成简单的字符。 比如\n是特殊含义,而\m没有,\m只会被看成是一个字面上的\m组成的长度为 2 的字符串。

    1.3 原始字符串

    在某些特定情况下希望字符是所见即所得。一个\n组合在一起就是表示两个字符,而不是换行符。此时可以用 Python 的原始字符串(Raw String),在要处理的字符前面加上r

    In [1]: len(r"\n")
    Out[1]: 2
    In [2]: len(r"\")
      File "<ipython-input-6-aa225c032aa6>", line 1
        len(r"\")
    SyntaxError: EOL while scanning string literal
    In [3]: len(r"\\")
    Out[3]: 2
    

    原始字符串并非万能:单独的反斜杠\没法用原始字符串表示,2 中把最后的引号给转义掉,3 是一个原始字符串,但表示两个反斜杠。无法用原始字符串表是转义序列

    2 Python 正则引擎

    用于匹配的程序叫正则表达式引擎,Python 自带re的模块。当然还有些第三方库正则引擎,但并不是主要介绍的对象。需要提前导入re模块:

    In [1]: import re
    

    同时,关于匹配的详细细节也不是本文介绍的重点。本文主要介绍正则引擎常用函数的使用,常见函数有两种用法:

    re.func(REG, string, flag)  # call the func with re directly
    re.compile(REG, flag).func(string)  # call the compiled object
    

    通常认为先 compile(compilere模块的函数),后运算可以提升效率。但实际上并不特别关注效率问题:

  • 某些高效的正则表达式可能难以理解
  • Python 本身运行就想对慢一些
  • 本文主要介绍使用,并不过分关注原理
  • 函数参数意义如下:

  • REG(regular expression)就是正则表达式,第三部分会详细介绍。
  • string 是要匹配的字符串,Python 字符串。
  • flag 是正则运算时候的参数,会在介绍具体函数时候介绍。
  • 常见的func是:

  • match 匹配字符串的开始,返回一个re.Match(匹配上)或None(没匹配上)
  • search 找到一个匹配就返回,即使有多个也只返回第一个,返回一个re.Match(匹配上)或者None(没匹配上)
  • findall 找到所有匹配,返回一个列表(第四部分详细介绍这个列表)
  • finditer 找到所有匹配,返回一个迭代器
  • split 对字符串按照一定规则切分,返回一个列表
  • sub 对字符串进行替换,返回替换后的字符串
  • subn 对字符串进行替换,返回 tuple,前面是替换后的字符,后面是总共替换次数
  • 所谓re.Match,有如下方法:

    re.match.span()  # span, tuple
    re.match.start()  # start index
    re.match.end()  # end index
    re.match.group()  # match group
    re.match.group_dict()  # match group
    

    其中group表示匹配的组, 会在后面捕获组一节中详细介绍。这里可以通过一个例子就re.Match进行简单说明。由于使用例子要用到正则表达式,这里先给出正则表达式的一条规则:大部分字符都匹配自身

    In [1]:x = re.search("abc", 'zyxabcdre')
    In [2]: x
    Out[2]: <re.Match object; span=(3, 6), match='abc'>
    In [3]: x.start()
    Out[3]: 3
    In [4]: x.end()
    Out[4]: 6
    In [5]: x.span()
    Out[5]: (3, 6)
    In [6]: x.group()
    Out[6]: 'abc'
    In [7]: x.group(0)
    Out[7]: 'abc'
    In [8]: x.groupdict()
    Out[8]: {}
    In [9]: re.match("abc", 'zyxabcdre')
    

    match匹配的是开始,如果开始没有匹配到,就返回None;而search可以匹配字符串的中间部分,匹配到一个就返回。

    flag可以给匹配添加更多选项,常见的flag如下:

    3 正则表达式语法

    这也是通常说的正则表达式,正则表达式用 Python 字符串实现,它们属于 Python 字符串,正则引擎处理正则表达式时候有额外的语法

    从第二部分知道,大部分字符都匹配它们自己,少数字符可以匹配其它字符串,它们需要被重点关注:

    3.1 匹配单个字符

    3.1.1 大部分字符匹配自己

    匹配大小写敏感,可添加re.IGNORECASE通配大小写

    In [1]: re.search('a', 'ABCD')
    In [2]: re.search('a', 'ABCD', re.IGNORECASE)
    Out[2]: <re.Match object; span=(0, 1),  match='A'>
    

    转义序列也包含其中,但不包含反斜杠

    In [1]: re.search('\n', 'ab\ncd')
    Out[1]: <re.Match object; span=(2, 3),  match='\n'>
    

    对反斜杠的特殊处理

    确实存在匹配单个字面意义上的反斜杠的情况,如匹配如下$LaTeX$:

    \section{back slash}
    

    若按普通转义序列一样处理反斜杠,即用\\作为正则表达式,则会报错:

    In [1]: re.search("\\", "\\x")
    ---------------------------------------------------------------------------
    error                                     Traceback (most recent call last)
    <ipython-input-2-ace9affb99aa> in <module>
    ----> 1 re.search("\\", "\\x")
    /usr/local/lib/python3.10/re.py in search(pattern, string, flags)
        199     """Scan through string looking for a match to the pattern, returning
        200     a Match object, or None if no match was found."""
    --> 201     return _compile(pattern, flags).search(string)
        203 def sub(pattern, repl, string, count=0, flags=0):
    /usr/local/lib/python3.10/re.py in _compile(pattern, flags)
        302     if not sre_compile.isstring(pattern):
        303         raise TypeError("first argument must be string or compiled pattern")
    --> 304     p = sre_compile.compile(pattern, flags)
        305     if not (flags & DEBUG):
        306         if len(_cache) >= _MAXCACHE:
    /usr/local/lib/python3.10/sre_compile.py in compile(p, flags)
        762     if isstring(p):
        763         pattern = p
    --> 764         p = sre_parse.parse(p, flags)
        765     else:
        766         pattern = None
    /usr/local/lib/python3.10/sre_parse.py in parse(str, flags, state)
        940     # parse 're' pattern into list of (opcode, argument) tuples
    --> 942     source = Tokenizer(str)
        944     if state is None:
    /usr/local/lib/python3.10/sre_parse.py in __init__(self, string)
        230         self.index = 0
        231         self.next = None
    --> 232         self.__next()
        233     def __next(self):
        234         index = self.index
    /usr/local/lib/python3.10/sre_parse.py in __next(self)
        243                 char += self.decoded_string[index]
        244             except IndexError:
    --> 245                 raise error("bad escape (end of pattern)",
        246                             self.string, len(self.string) - 1) from None
        247         self.index = index + 1
    error: bad escape (end of pattern) at position 0
    

    原因如下:正则引擎首先对正则表达式处理,把\\(两个连续反斜杠)翻译为\(一个反斜杠),此时正则表达式变成\"(一个反斜杠结合后面的引号),正则表达式缺少字符串结束标志符--引号",报错。(正则引擎对正则表达式还会有其它处理,后面会介绍。)正确姿势是用四个反斜杠

    In [1]: re.search("\\\\", "\\subgraph")
    Out[1]: <re.Match object; span=(0, 1), match='\\'>
    

    好家伙!为匹配一个字面意义上的反斜杠,需要在正则表达式中用四个连续反斜杠。这也是所谓的反斜杠灾难。为了避免复杂的写法,正则表达式中也有原始字符串(Raw String),作用是:正则引擎处理正则表达式时候,不把连续两个反斜杠\\翻译为一个反斜杠\

    In [1]: re.search(r"\\", "\\x")
    Out[1]: <re.Match object; span=(0, 1), match='\\'>
    

    为了让正则表达式和 Python 字符串更加接近,一条推荐的规则是:不管多么简单的规则,都采用原始字符串的方式进行匹配。

    3.1.2 点.匹配所有字符

    .匹配所有字符,这个和通配符中星号*表示所有字符一样。通常点并不能匹配换行符,加入re.DOTALL就可以了。

    需要注意的是这里说的是单个字符。

    3.1.3 []表示集合

    中括号([])表示范围,匹配中括号中任意一个元素。中间是或的关系,元素之间不需要隔开。

    In [1]: re.search(r'[1234]','843')
    Out[1]: <re.Match object; span=(1, 2), match='4'>
    

    中括号中的元素可以重复,但是并没有特殊意义:

    In [1]: re.search(r'[ddxs]','d')
    Out[1]: <re.Match object; span=(0, 1), match='d'>
    

    中括号中大部分字符(包含转移序列)都只表示原本意思,除了特殊字符:点(.)可以匹配任意字符,但在中括号([])中却只匹配字面上的点(.):

    In [1]: re.search(r'.', ',')
    Out[1]: <re.Match object; span=(0, 1), match=','>
    In [2]: re.search(r'[.]', ',')
    In [3]: re.search(r'[\n]', 'x\n')
    Out[3]: <re.Match object; span=(1, 2), match='\n'>
    

    对某些需要转义表示的字符,也可以放到中括号中来避免使用转义符号,比如反斜杠:

    In [1]: re.search(r"[\\]", '\\x')
    Out[1]: <re.Match object; span=(0, 1), match='\\'>
    

    需要特殊关注的字符^-[,规则如下:

  • ^符号表示取反,但是^一定要添加在最开始
  • In [1]: re.search(r'[^1234]','1843')
    Out[1]: <re.Match object; span=(1, 2), match='8'>
    

    如果^在中间,则只会被当成是一个普通字符

    In [1]: re.search(r'[7^1234]','1843')
    Out[1]: <re.Match object; span=(2, 3), match='1'>
    
  • 中括号中使用-表示范围,实现对表达式的精简
  • r"[0-9]" # equal  r"[0123456789]" in regular expression
    r"[a-z]" # equal r"[abcdefghijklmnopqrstuvwxyz]"in regular expression
    

    需注意若-前后不是范围,则只会匹配普通的-符号:

    In [1]: re.search("[-a]", "b-")
    Out[1]: <re.Match object; span=(1, 2), match='-'>
    
  • 中括号开始标志[ 如果非要表示字面意义上左中括号([),则要使用\[
  • In [1]: re.search(r"[]]", ']')
    Out[1]: <re.Match object; span=(0, 1), match=']'>
    In [2]: re.search(r"[[]", ']')
    <ipython-input-44-863737df3ef5>:1: FutureWarning: Possible nested set at position 1
      re.search(r"[[]", ']')
    

    当要处理(字面意义)中括号时候,最好通通用转义序列

    3.1.4 正则表达式转义序列

    它们虽然是反斜杠(\)和某个字符的组合,但不是Python 转义序列:只有正则引擎会对它们特殊处理,而在其它情况下只会被看成是普通字符串。它们也可看成对中括号([])的扩展。如下正则表达式含义比较清楚:

    r1 = "[0-9]"
    r2 = "[a-zA-Z_]"
    

    r1表示一位十进制数,r2表示任意字母数字或下划线。可以用正则表达式转义序列进一步简写:

    r1 = "\d"
    r2 = "\w"
    

    更多的正则表达式转义序列如下:

    前面介绍正则表达式原始字符串时候提到,正则引擎会对首先正则表达式分析,所谓的正则表达式转义字符就是被正则引擎进行处理。

    3.2 字符串匹配

    实际中更有用的是字符串的匹配。如同字符串对字符的扩展方式,正则表达式并列排布,则匹配并列排布的字符和字符串:

  • 若正则表达式regAregB分别匹配字符chaAchaB,则正则表达式regAregB匹配字符串chaAchaB
  • 若正则表达式regAregB分别匹配字符串strAstrB,则正则表达式regAregB匹配字符串strAstrB
  • reg1 = r"\d"
    reg2 = r"\d\d"
    reg3 = r"\d\w"
    re.search(reg1, "56")  # 5
    re.search(reg2, "56")  # 55
    re.search(reg3, "5x")  # 5x
    

    3.2.1 小括号界定正则表达式范围

    并列排布的运算并不都是从左往右计算,而是依据运算的优先级从高到低进行运算。为了说明优先级会影响匹配的结果,这里引入一个优先级极低的运算符号|,它表示或的关系。

    In [1]: reg1, reg2 = r'a', r'b|c'
    In [2]: re.match(r'ab|c', 'ac')
    In [3]: re.match(r'ab|c', 'c')
    Out[3]: <re.Match object; span=(0, 1), match='c'>
    In [4]: re.match(r'ab|c', 'ab')
    Out[4]: <re.Match object; span=(0, 2), match='ab'>
    

    直接把reg1reg2组合到一起,并不能匹配ac,只能匹配ab或者c了。实际上匹配的优先级被修改。字符组合优先级高于或(|)运算。此时如果要实现匹配ac,可以添加小括号(实现:

    In [1]: re.match(r'a(b|c)', 'ac')
    Out[1]: <re.Match object; span=(0, 2), match='ac'>
    

    小括号界定正则表达式的范围,改变了匹配优先级。添加非字面意思的小括号并不是匹配小括号的意思。

    In [1]: re.search(r'x|y|z', "z")
    Out[1]: <re.Match object; span=(0, 1), match='z'>
    In [2]: re.search(r'(x)|(y)|(z)', "z")
    Out[2]: <re.Match object; span=(0, 1), match='z'>
    

    同时,小括号可以把一个正则表达式界定为一个组(group)。后续可以对这个组进行操作。下一节中将对组进行说明。

    3.2.2 重复匹配

    根据前面的规则,下面的正则表达式用于匹配多位十进制数:

    \d\d \d\d\d \d\d\d\d

    如果要匹配位数更多的数,则需要加长正则表达式。显然这样并不方便,于是就有了如下简写:

    (REG){m,n} # 至少m次,至多n次
    (REG){,n} # 至少0次,至多n次
    (REG){m,} # 至少m次
    (REG){m} # 只能是m次
    

    特殊情况下可继续简写:

    (REG){0,1} -> (REG)?
    (REG){1,} -> (REG)+
    (REG){0,} -> (REG)*
    

    上面检索都是贪婪的:会尽可能长地匹配。在正则表达式后面加上?实现非贪婪搜索:

    In [1]: re.search(r'm{2,3}', "mmm")
    Out[1]: <re.Match object; span=(0, 3), match='mmm'>
    In [2]: re.search(r'm{2,3}?', "mmm")
    Out[2]: <re.Match object; span=(0, 2), match='mm'>
    

    除了对单个字符这样操作,也可以把用括号,把多个正则表达式作为一组进行操作:

    In [1]: re.search(r'(ab)+', 'abababxxx')
    Out[1]: <re.Match object; span=(0, 6), match='ababab'>
    In [2]: re.search(r'ab+', 'abababxxx')
    Out[2]: <re.Match object; span=(0, 2), match='ab'>
    

    1 把ab看成一个整体,进行重复匹配;2 是匹配a和多个b。这里括号让一系列正则表达式组成一个组,然后对整个组进行操作。

    3.3 捕获组、非捕获组与命名组

    3.3.1 小括号与捕获组

    总结一下小括号作用:

  • 界定正则表达式范围
  • 改变匹配的优先级
  • 把一系列正则表达式匹配的结果对应到组(group)中
  • REG是一个合法的正则表达式:

    (REG)
    

    此时REG匹配到的结果就会存到一个 group 中,可以通过group函数进行访问。

    In [1]: x = re.search(r'((\d)\w(\d))', "a1b3x")
    In [2]: x.group(0)
    Out[2]: '1b3'
    In [3]: x.group(1)
    Out[3]: '1b3'
    In [4]: x.group(2)
    Out[5]: '1'
    In [5]: x.group(3)
    Out[5]: '3'
    In [6]: x.group(4)
    ---------------------------------------------------------------------------
    IndexError                                Traceback (most recent call last)
    <ipython-input-29-b8b09168b4e1> in <module>
    ----> 1 x.group(4)
    IndexError: no such group
    In [7]: x.group(1, 3)
    Out[7]: ('1b3', '3')
    

    以正则表达式最左边为1,从左往右开始计数,(非转义)左括号(()排列的顺序就是 group 函数访问时候的参数,此时返回当前左括号和对应右括号中的正则表达式匹配的结果。group()group(0)等价,都表示整个正则表达是匹配的结果。

    In [1]: x = re.search(r'\d', '5')
    In [2]: x.group(0)
    Out[2]: '5'
    In [3]: x.group()
    Out[3]: '5'
    

    这种用小括号包围正则表达式得到的组就是捕获组(Capture Group)。捕获组的结果会存储到内存中,可以通过 group 的 index 进行访问。

    3.3.2 非捕获组

    对于组而言,存储匹配结果并且访问并非一定必要:有时仅仅想知道有这么个匹配(并不想知道具体匹配结果),或者说存储结果这个过程占用空间、影响速度而变得不可以。此时就引入非捕获组(Non Capture Group),只表示匹配关系,不存储具体结果

    (?:REG)
    

    非捕获组也使用了小括号()来界定范围,单非捕获组正则表达式前面会有问号(?)和冒号(:)。

    问号(?)来自 Perl(大部分语言的正则表达式实现都受 Perl 影响)。Perl 从 4 升级到 5 时,为了保持兼容性引入了(?的写法:对一个非转义的左括号(()用问号(?)进行重复匹配没有意义,换言之(?并不会引起歧义; 从另一个角度来看,不会有一个合法的正则表达式以问号(?)开头。

    冒号(:)表示普通非捕获组,表示匹配当前位置的字符(串)。当然,还有其它非捕获组,如后向断言(lookbehind)和前向断言(lookahead)。

    非捕获组不会计入捕获组的 index 中,无论是否同时存在捕获组:

    In [1]:  x = re.search(r'((?:\d)\w(\d))', "a1b3x")
    In [2]: x.group(0,1,2)
    Out[2]: ('1b3', '1b3', '3')
    In [3]: x.group(3)
    ---------------------------------------------------------------------------
    IndexError                                Traceback (most recent call last)
    <ipython-input-9-6cd92edb32bb> in <module>
    ----> 1 x.group(3)
    IndexError: no such group
    

    group(2)匹配最后一个3,没有group(3)。非捕获组不计入 group 结果只针对非捕获组所在的括号,但对非捕获组外面或者内部的捕获组并没有影响

    In [1]: x = re.search(r'([a-c])(?:[d-f]([g-h]))','adg')
    In [2]: x.group(0,1,2)
    Out[2]: ('adg', 'a', 'g')
    

    3.3.3 命名组

    有时除了用 index 来访问匹配内容,还希望赋予匹配一个有意义的名字。此时可以用命名组(Named Group),它属于捕获组,主要解决组命名的问题。

    (?P<name>REG)
    

    P表示这是Python 正则表达式的语法(而非来自 Perl)。命名组是一种捕获组,之前捕获组使用 index 来访问 group 的方式依旧有效。命名组在此之外添加了用名字访问的方式,同时会获得一个非空的groupdict

    In [1]: x = re.search(r'(?P<hh>[a-c])(?:[d-f]([g-h]))','adg')
    In [2]: x
    Out[2]: <re.Match object; span=(0, 3), match='adg'>
    In [3]: x.group(0,1,2)
    Out[3]: ('adg', 'a', 'g')
    In [4]: x.group('hh')
    Out[4]: 'a'
    In [5]: x.groupdict()
    Out[5]: {'hh': 'a'}
    

    3.3.4 组的应用--匹配重复的字符(串)

    如果要匹配连续重复的字符串,则可以用\{num},其中{num}是数字,一定要是一个存在的 index,且不能是0,这也要求要重复的对象一定要在小括号中。

    In [1]: re.search(r'(\d)\1{2}', '21112121211')
    Out[1]: <re.Match object; span=(1, 4), match='111'>
    

    上面的\1中的1就是 group 的 index。

    In [1]: re.search(r'(\d)\0{2}', '21112121211')
    In [2]: re.search(r'\d\0{2}', '21112121211')
    In [3]: re.search(r'(\d)\2{2}', '21112121211')
    ---------------------------------------------------------------------------
    error                                     Traceback (most recent call last)
    <ipython-input-32-86e83c2fdb65> in <module>
    ----> 1 re.search(r'(\d)\2{2}', '21112121211')
    /usr/lib64/python3.9/re.py in search(pattern, string, flags)
        199     """Scan through string looking for a match to the pattern, returning
        200     a Match object, or None if no match was found."""
    --> 201     return _compile(pattern, flags).search(string)
        203 def sub(pattern, repl, string, count=0, flags=0):
    /usr/lib64/python3.9/re.py in _compile(pattern, flags)
        302     if not sre_compile.isstring(pattern):
        303         raise TypeError("first argument must be string or compiled pattern")
    --> 304     p = sre_compile.compile(pattern, flags)
        305     if not (flags & DEBUG):
        306         if len(_cache) >= _MAXCACHE:
    /usr/lib64/python3.9/sre_compile.py in compile(p, flags)
        762     if isstring(p):
        763         pattern = p
    --> 764         p = sre_parse.parse(p, flags)
        765     else:
        766         pattern = None
    /usr/lib64/python3.9/sre_parse.py in parse(str, flags, state)
        947     try:
    --> 948         p = _parse_sub(source, state, flags & SRE_FLAG_VERBOSE, 0)
        949     except Verbose:
        950         # the VERBOSE flag was switched on inside the pattern.  to be
    /usr/lib64/python3.9/sre_parse.py in _parse_sub(source, state, verbose, nested)
        441     start = source.tell()
        442     while True:
    --> 443         itemsappend(_parse(source, state, verbose, nested + 1,
        444                            not nested and not items))
        445         if not sourcematch("|"):
    /usr/lib64/python3.9/sre_parse.py in _parse(source, state, verbose, nested, first)
        524         if this[0] == "\\":
    --> 525             code = _escape(source, this, state)
        526             subpatternappend(code)
    /usr/lib64/python3.9/sre_parse.py in _escape(source, escape, state)
        421                 state.checklookbehindgroup(group, source)
        422                 return GROUPREF, group
    --> 423             raise source.error("invalid group reference %d" % group, len(escape) - 1)
        424         if len(escape) == 2:
        425             if c in ASCIILETTERS:
    error: invalid group reference 2 at position 5
    

    3.4 关系匹配

    有时候希望匹配的字符串前面或者后面满足一些条件,但是对条件本身的内容并不关心。此时就要用到关系匹配。

    关系匹配也可以认为是零宽度匹配,返回宽度为零的字符串。

    3.4.1 开头结尾匹配

    ^$分别匹配开头和结尾:

    In [1]: re.search(r"^c\w", 'cacb')
    Out[1]: <re.Match object; span=(0, 2), match='ca'>
    In [2]: re.search(r"\w$", 'cacb')
    Out[2]: <re.Match object; span=(3, 4), match='b'>
    In [3]: re.search(r"https|http","https")
    Out[3]: <re.Match object; span=(0, 4), match='http'>
    In [4]: re.search(r"(http)|(https)", "https")
    Out[4]: <re.Match object; span=(0, 4), match='http'>
    

    更宽泛的条件是边界匹配,可用\b来表示正则表达式。

    3.4.2 后向断言(lookbehind)

    实际上要求字符串前面满足一些条件。 这翻译容易让人迷惑,估计这里是说要着重观察后面的结果并且返回。

    (:<=REG)  # positive
    (:<!REG)  # negative
    
    In [1]: re.search(r'(?<=\d)abc', 'abc1abcdabc\n')
    Out[1]: <re.Match object; span=(4, 7), match='abc'>
    In [2]: re.search(r'(?<!\d)abc', 'abc1abcdabc\n')
    Out[2]: <re.Match object; span=(0, 3), match='abc'>
    In [3]: re.search(r'(?<=c)(?<=\d)abc', 'abc1abcdabc\n')
    

    1 要求前面一定要有一个 \d;而 2 正好相反,只返回 4~7 处的abc

    后向断言是一种零宽度匹配,它匹配长度为零的字符(串)。按照前面正则表达式的排列规则,多个正则表达式并列排布表示一起匹配。由于零宽度字符相加还是零款度字符,多个后向断言并列排布,还是匹配一个结果。此时就起到了一个与运算的效果。

    In [1]: re.search(r'(?<=c\d)abc', 'abc1abcdabc\n')
    Out[1]: <re.Match object; span=(4, 7), match='abc'>
    

    3.4.2 前向断言(lookahead)

    后向断言对称操作,表达式后面要满足一定条件。

    (?=REG)  # positive
    (?!REG)  # negative
    

    这两种断言也可以联合使用:

    In [1]: re.search(r'(?<!\d)abc(?=\d)', 'abc1abcdabc\n')
    Out[1]: <re.Match object; span=(0, 3), match='abc'>
    

    3.4.3 用断言来拼凑正则表达式取反

    如何用正则表达式来确认一个字符串中没有另外的字符串?

    3.5 正则表达式优先级

    如同编程语言的运算都有运算优先级一样,正则表达式也有优先级,当正则表达式遇到一起时,就需要考虑其优先级。 下表列出了优先级,从上到下递减,从左到右递减:

  • \ 转义字符
  • (), (?:), (?=), (?!),[]
  • \*, +, ?, {n}, {n,}, {n,m}
  • ^, $, \任何元字符、任何字符 定位点和序列(即:位置和顺序)
  • | "或"操作字符
  • 4 其它函数

    4.1 re.findall找到所有匹配

    re.findall返回所有匹配结果组成的列表:

    In [1]: re.findall(r'[a-z][A-Z]', 'aAxxxxxxbBxxxxxx')
    Out[1]: ['aA', 'bB']
    In [2]: re.findall(r'([a-z])([A-Z])', 'aAxxxxxxbBxxxxxx')
    Out[2]: [('a', 'A'), ('b', 'B')]
    

    若正则表达中没有捕获组,返回列标的元素是整个匹配(group()group(0)),如上面的 1;如果有捕获组,则列表元素是所有 group 组成的 tuple,如上面的 2。

    4.2 re.split分割字符串

    re.split用于对字符串进行切片。比 Python 自带的str.split功能更加强大。

    re.split(reg, string, maxsplit=0)
    

    maxsplit的意思是最多切分次数,默认值0表示所有都切分。

    In [1]: re.split(r"\d", "20python21")
    Out[1]: ['', '', 'python', '', '']
    In [2]: re.split(r"\d", "20python21", 1)
    Out[2]: ['', '0python21']
    

    split的结果包含空字符串。

    4.3 re.sub替换字符串

    re.sub 用于匹配对象替换,sub是 substring 的意思。

    re.sub(reg, repl, string)
    

    其中repl是替换方式,可以是固定字符串、函数或者lambda表达式,对于后面两种情况,操作的对象是match group

    In [1]: re.sub(r'\d', 'NUM', "hello world 5")
    Out[1]: 'hello world NUM'
    
    In [1]: re.sub(r'\d', lambda x: str(int(x.group()) + 3), "hello world 5")
    Out[1]: 'hello world 8'
    

    字符串替换函数replace替换对象只能是固定的字符串,而正则表达式扩展了替换的对象。多次调用replace的结果并不一定和re.sub等价。

    sub相似的函数是subn,返回一个 tuple,两个元素依次是替换后的字符串和替换次数:

    In [1]: re.subn(r'\d', lambda x: str(int(x.group()) + 3), "hello world 5")
    Out[1]: ('hello world 8', 1)
    

    5 正则表达式实战

  • 自动给 markdown 标题编号
  • 自动给代码中的 IPython 输入输出编号
  • 6 参考文献

  • Regular Expression HOWTO
  • re — Regular expression operations
  •