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
,他们通常需要被 """"""
来包括,其中还可以添加注释:
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: 编译标志控制则正则表达式的工作方式
上面的 p 就是我们构建的正则表达式对象 re.Pattern
,这个正则表达式对象提供了很多方法可以让我们进行匹配、查找子串、替换等等操作。
正则表达式对象应用匹配
当我们获取了一个正则表达式对象(re.Pattern
)之后,就可以让他与字符串进行匹配。在 python 中提供了四个匹配函数:
方法 | 含义 |
---|---|
Pattern.match() |
正则匹配从字符串头开始匹配 |
Pattern.search() |
正则匹配可以是字符串的任意位置(当然可以指定^ 来从头匹配) |
findall() |
找到正则匹配的所有子字符串,并将他们作为列表返回 |
finditer() |
找到正则匹配的所有子字符串的匹配对象作为迭代器返回 |
这四个函数他们的参数都是 (string [, pos [,endpos]])
:
- string: 匹配字符串是必须的
pos endpos
: 使用string[pos:endpos]
限制了匹配的开始和结束位置
而他们的返回结果不同:
match()
和search()
在匹配到结果后都会返回匹配对象,如果没有匹配到结果都会返回 Nonefinditer()
返回匹配对象的迭代器findall()
直接返回结果列表(列表中元素在没有捕获组时为字符串,如果存在捕获组则为元组)
match()和 search()
这两个方法最重要的区别就是,match()
从字符串头开始匹配,而 search()
则可以是字符串的任意位置:
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
: 最后一个匹配的命名组的名称,如果没有匹配则是 NoneMatch.lastindex
: 捕获组的最后一个匹配的整数索引值,如果没有捕获这是 NoneMatch.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...
,对于命名捕获也可以是捕获名称:
>>> 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
>>> 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
通常还是推荐构建正则表达式对象,因为它可以复用。