Skip to content

re

正则表达式最初由 ed/grep 实现,由 Perl 语言发扬光大。Python 的re模块提供了类似 Perl 语言的正则表达式匹配操作。

Tips

具体的语法参考正则表达式

原始字符串

转义序列都是以 \section 的形式表现的,此时如果我们想要匹配一个 \ 就必须使用 \\ 这样的形式,而如果想要匹配 \\section 就必须写成 \\\\section,一但要匹配的反斜杠过多就会导致反斜杠灾难。在 Python 中通过原始字符串(r"xxx")来解决这个问题,原始字符串内的任何 \ 都不会被转义而直接发送给引擎匹配(类似 shell 中的单引号):

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

Tips

当然这也不是一劳永逸的解决方案,因为它不能解决转义和非转义同时存在的情况。在这个时候 \\ 还是必须的。

编译标志

编译标志允许修改正则表达式的工作方式:

标志 含义
ASCII/A 使转义元字符 \w \b \s \d 匹配仅与 ASCII 字符匹配
DOTALL/S 使 . 匹配任意字符,包括换行符
IGNORECASE/I 进行大小写不敏感匹配
LOCALE/L 进行区域设置感知匹配
MULTILINE/M 多行匹配(影响 ^$ )
VERBOSE/X 启用详细的正则

这里面比较不好理解的是 re.VERBOSE 标志,其意义是编写更加可读的正则表达式,可以让你在正则表达式中包含空格回车等空白符来组织正则表达式。例如 dog | cat 等同于 dog|cat 但是可能更加可读些。需要注意的是 [a b] 依然匹配 a b,他们通常需要被 """""" 来包括,其中还可以添加注释:

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)

# 上面的可读性明显更强
pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")

Tips

还可以在表达式开头使用 (?ia) 这样的形式来开启,这也是正则表达式的标准用法。

使用正则表达式

在 python 中,re 模块提供了正则表达式引擎的接口。通常的流程就是编译正则表达式对象,然后进行匹配。

正则表达式对象

re.compile(regex, flags) 函数就是用来编译正则表达式对象的,其中:

  • regex: 就是正则表达式字符串
  • flags: 编译标志控制则正则表达式的工作方式
Python
import re
p = re.compile('[a-z]+')
type(p) # re.Pattern

上面的 p 就是我们构建的正则表达式对象 re.Pattern,这个正则表达式对象提供了很多方法可以让我们进行匹配、查找子串、替换等等操作。

正则表达式对象应用匹配

当我们获取了一个正则表达式对象(re.Pattern)之后,就可以让他与字符串进行匹配。在 python 中提供了四个匹配函数:

方法 含义
Pattern.match() 正则匹配从字符串头开始匹配
Pattern.search() 正则匹配可以是字符串的任意位置(当然可以指定^来从头匹配)
findall() 找到正则匹配的所有子字符串,并将他们作为列表返回
finditer() 找到正则匹配的所有子字符串的匹配对象作为迭代器返回

这四个函数他们的参数都是 (string [, pos [,endpos]]):

  • string: 匹配字符串是必须的
  • pos endpos: 使用 string[pos:endpos] 限制了匹配的开始和结束位置

而他们的返回结果不同:

  1. match()search() 在匹配到结果后都会返回匹配对象,如果没有匹配到结果都会返回 None
  2. finditer() 返回匹配对象的迭代器
  3. findall() 直接返回结果列表(列表中元素在没有捕获组时为字符串,如果存在捕获组则为元组)

这两个方法最重要的区别就是,match() 从字符串头开始匹配,而 search() 则可以是字符串的任意位置:

Python
p = re.compile("c");
p.match('abcdef');  # No match  因为match必须匹配开头
p.match('abcdef',2); # match 2指定了开始位置是c,所以成功匹配
p.search('abcdef'); # match <re.Match object; span=(2, 3), match='c'>
re.compile('^c').serch('abcdef'); # 实际上就等价于 p.match('abcdef')

# 注意在多行模式下(MULTILINE),match()只匹配字符串的开始,但是search()的^开头的正则表达式会匹配每行的开始。

匹配对象

使用 Pattern.match()Pattern.search() 的匹配成功的返回值都是匹配对象,即 re.Match 对象。该对象提供了很多属性和方法供使用:

  • Match.string: 匹配字符串,即 Pattern.match( ) 等方法的 string 参数
  • Match.re: 返回产生这个实例的正则对象
  • Match.lastgroup: 最后一个匹配的命名组的名称,如果没有匹配则是 None
  • Match.lastindex: 捕获组的最后一个匹配的整数索引值,如果没有捕获这是 None
  • Match.pos: 开始搜索的位置,实际上就是传递给 search() 等方法的 pos 参数
  • Match.endpos: 结束搜索的位置,实际上就是传递给 search() 等方法的 endpos 参数
  • Match.group([group1, group2...]): 返回一个或多个匹配的子组,如果只有一个参数,结果就是一个字符串,如果是多个参数结果就是一个元组。没有参数结果就是 group(0) 即整体匹配的结果
  • Match.groups(): 返回一个元组,包含所有匹配的子组,需要注意的是不包括 group(0) 即整体匹配字符串
  • Match.groupdict(): 返回一个字典,包含所有的命名子组
  • Match.start(group): 返回 group 组的子串的开始索引
  • Match.end(group): 返回 group 组的子串的结束索引
  • Match.span(group): 实际上就是 (m.start(group),m.end(group))

匹配对象中最重要的方法就是 Match.group(),该方法接受一个组名作为参数然后返回该组匹配的结果,其中 group(0) 比较特殊,它是整个正则匹配的字符串,而根据其他捕获 () (?P<name>...) 的顺序组名可以是 1 2 3...,对于命名捕获也可以是捕获名称:

Python
>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m.group(0)       # The entire match
'Isaac Newton'
>>> m.group(1)       # The first parenthesized subgroup.
'Isaac'
>>> m.group(2)       # The second parenthesized subgroup.
'Newton'
>>> m.group(1, 2)    # Multiple arguments give us a tuple.
('Isaac', 'Newton')
>>> m.groups()
('Isaac', 'Newton')


>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.group('first_name')
'Malcolm'
>>> m.group('last_name')
'Reynolds'
>>> m.group(1)
'Malcolm'
>>> m.group(2)
'Reynolds'
>>> m.groupdict()
{'first_name': 'Malcolm', 'last_name': 'Reynolds'}

Tips

正则匹配的重点就在于整体的匹配以及内部使用括号捕获的匹配。

修改字符串

正则表达式对象还提供了两个修改字符串的方法:

  • Pattern.split(string): 根据正则表达式匹配到的字符串来拆分 string
  • Pattern.sub(replacement, string): 将匹配到的字符串替换为 replacement
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().']

>>> p = re.compile('(blue|white|red)')
>>> p.sub('colour', 'blue socks and red shoes')
'colour socks and colour shoes'

# subn() 等同于 sub() 不过会返回一个包含替换次数的元组
>>> p.subn('colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)

模块级函数

re 的标准用法是通过 re.compile() 来构建一个正则表达式对象,然后通过这个对象中提供的方法来进行搜索、修改等操作。实际上 re 在模块级别中就提供了 re.search() re.match() re.finditer() re.split() re.sub() 方法,他们可以看作是一种快捷的匹配方法,与构建正则表达式对象的区别在于这些方法的第一个参数都是 regex 即接受一个正则表达式字符串。

Tips

通常还是推荐构建正则表达式对象,因为它可以复用。