关于正则表达式的预备知识,可以参见:正则表达式简单入门
本文介绍正则表达式在Python中的应用,主要通过标准库 re
提供的函数实现。
使用 re 库的预备知识
字符转义问题
由于正则表达式使用反斜杠 \
来转义字符,而 Python 也使用 \
来转义字符。而在这种情况下,两者的转义可能发生冲突:
由于 Python 会先将正则表达式编译成一个字符串,再利用 re
库的正则表达式语法去匹配一个字符串如 target_string
,所以 Python 的转义级别要高于正则表达式的。因此在本例中:
- Python 会先将表达式
expr01
中的"\b"
转义成一个退格符,再将"退格符on"
作为一个正则表达式去匹配目标字符串,因此得到的结果是什么也匹配不到。 - Python 会先将表达式
expr02
中的"\\"
转义成反斜杠"\"
,再将"\bon"
作为一个正则表达式,而正则表达式又将"\b"
转义成单词的边界,即完整的表达式为"单词的边界on"
去匹配目标字符串,因此匹配的结果是"on"
在上例可以看出,Python 和正则表达式的转义混合会引起混乱。为了避免这种情况发生,强烈建议使用 Python 的原始字符串,它通过在字符串字面量加上 r
前缀来表明原始字符, 该前缀会忽略 Python 的转义,从而可以准确地表达一个正则表达式。因此,上例可以写成:
常用参数
要用上正则表达式,应该要提供一个正则表达式和待匹配的文本。re
库的函数通常都有以下三个常用的参数:pattern
、string
和 flag
:
pattern
:一个代表正则表达式的字符串,建议使用 r 不转义。string
:需要匹配的字符串,如果字符串过长(如 HTML 源码),一般使用变量指向该字符串。flags
:匹配模式。常见flags
的取值及含义如下:
re.I | 忽略大小写模式 |
re.M | 多行模式,使 ^ 和 $ 能够作用于每行的开始和结尾。该模式下便有了 ^ 和 \A 以及 $ 和 \Z 的区别。 |
re.S | 单行模式,使 . 所匹配的任意字符包含换行符 |
re.X | 冗长模式,忽略空格,并允许用 # 号添加注释 |
re.A | 使用 ASCII 字符集中定义的 \w 、\W 、\b 、\B 、\s 、\S ,而不是默认的 Unicode 字符集。 |
re.L | 本地化模式,使用当前本地化语言字符集中定义的 \w 、\W 、\b 、\B 、\s 、\S (用于多语言操作系统) |
re.U | 使用 Unicode 字符集中定义的 \w 、\W 、\b 、\B 、\s 、\S ,默认的情况 |
如果要同时设置多个匹配模式,使用按位或运算符 |
号将其隔开。例如 flags=re.M|re.X
。
基本用法:匹配和搜索
匹配
match(pattern, string, flags=0)
函数使用 pattern
传入的正则表达式按 flags
匹配模式,从字符串的起始位置逐一匹配字符串 string
。若匹配成功,返回一个 Match
对象;若匹配不成功,返回一个空值 None
。例如:
结果为:
由于 Python 默认一个对象在条件判断时代表布尔值 True
,而空值代表布尔值 False
,为了防止匹配失败造成的影响,可以使用 if
语句来判断 match()
函数的匹配是否成功。
如果 match()
函数匹配成功,它将返回一个 Match
对象。对于这个 Match
对象,可以使用一些方法来获取正则表达式匹配的信息。
当匹配的表达式中有用到圆括号 ( )
或 (?P<name>)
进行分组时,可以使用 .group()
方法用来获取各组匹配到的字符串。括号内传入的参数为代表组序号的整数,或代表组名称的字符串(只需要名称就够了,不需要别的修饰符)。如果不传入参数或传入 0
,则代表获取整个匹配结果。如果传入的值没有对应的组,会发生 IndexError
错误。例如:
结果为:
类似地,还可以使用 .groups()
方法得到分组匹配到的字符串元组,或者通过 .groupdict()
方法得到一个以自定义名称作为键、匹配结果作为值的字典。如果匹配的表达式中没有自定义名称的分组,后者返回一个空字典。
以下给出了这样一个示例:
结果为:
该对象还有以下常用的方法:
.start(group)
:返回对应组开始的匹配位置,整个字符串的开头位置为 0 向后类推。忽略group
参数相当于返回整个匹配结果的表达式的开头位置。由于match()
函数会从整个字符串的开头匹配,所以不带参数或者带参数 0 时会返回 0 。.end(group)
:返回对应组结尾的匹配位置,整个字符串的开头位置为 0 向后类推。忽略group
参数相当于返回整个匹配结果的表达式的结尾位置。.span(group)
:以元组的形式返回对应组的开头和结尾的匹配位置,相当于(.start(group), .stop(group))
它的一些属性可以反过来查找匹配时用到的信息:
re
:匹配时使用的正则表达式对象string
:待匹配的文本pos
:正则表达式搜索文本的开始位置endpos
:正则表达式搜索文本的结束位置lastindex
:正则表达式最后的组序号lastgroup
:正则表达式最后的组名
fullmatch(pattern, string, flags=0)
函数和 match()
函数相似,只不过 fullmatch()
函数用来检查正则表达式和目标字符串是否完全匹配,而不是部分匹配。
如果完全匹配,则 fullmatch()
函数返回一个 Match
对象,否则便返回空值。
其参数、Match
对象的属性等都和 match()
函数几乎一致。
但是有一个例外:在懒惰匹配模式下,如果其完整的匹配结果是原字符串,则 fullmatch()
也能够成功匹配。这是由于懒惰匹配必须要先匹配完整的字符串,再回溯选取最少的结果。以下给出了一个简单的演示:
结果为:
搜索
相比与匹配,搜索可能应用更多一些。search(pattern, string, flags=0)
函数也和 match()
函数相似,该函数会检索整个字符串来寻找目标表达式的匹配对象。
search()
函数和 match()
函数的区别在于配对位置。match()
函数只会匹配字符串的开始,如果字符串的开始不符合正则表达式,便匹配失败。而 search()
函数会检索整个字符串来判断里面是否有结果满足正则表达式,如果字符串的某一部分符合正则表达式,便匹配成功。
该函数也会得到一个 Match
对象。
结果为:
可以看到,得到的结果可以通过 .span()
方法来得到匹配字符串的位置,或者通过 .group()
方法得到完整的匹配字符串。
注意:search()
函数虽然可以搜索整个字符串,但是它只会返回第一个匹配成功的结果,例如:
结果为:
尽管上例按照正则表达式的匹配方式来说,"cat"
、"catches"
、"cab"
都可以成功匹配。
如果想到获取所有的匹配结果,可以使用 findall(pattern, string, flags=0)
函数。该函数可以在字符串中找到正则表达式所匹配的所有子串,并返回一个包含所有结果的列表。如果没有找到匹配的结果,则返回空列表;若 pattern
中包含组,则返回各组匹配结果的列表。
以下给出了这样一个示例:
结果为:
对于以上这种每个正则表达式只包含一个分组时的情况,findall()
返回的列表中的每个元素都是该唯一分组匹配到的字符串。如果包含两个或以上分组,那么每个分组匹配到的字符串就以元组的形式排开了。
finditer(pattern, string, flags=0)
函数的作用与 f
findall() 函数类似。只不过当被匹配的对象很长(如一个大型网站的 HTML 源码),可能会匹配出非常多的结果。这个时候不希望直接返回一个很长的列表,便需要用到 finditer()
函数返回一个生成器来存储匹配结果,其中每个迭代元素都是 Match
对象。
以下给出了一个这样的示例:
结果为:
替换与分隔
替换
正则表达式可以用来替换字符串中不符合要求的部分。使用 sub(pattern, repl, string, count=0, flags=0)
函数可以实现该效果。以下是该函数的参数说明:
pattern
:一个符合正则表达式的要替换字符串repl
:用来替换的字符串,也可为一个函数string
:被查找并替换的原始字符串count
:模式匹配后从前向后替换的最大次数,默认为 0 ,表示替换所有的匹配flags
:编译时用的匹配模式
该函数返回替换后的结果。以下给出了这样一个示例:
结果为:
一种常见的需求就是对捕获到的结果做一些小改动,而不是完全替换为一个毫不相干的结果。这可以通过引用组实现。repl
参数内,可以通过 \g<group>
的形式引用一个分组的匹配结果。
以下给出了这样的一个示例:
结果为:
repl
参数有一种特殊情况就是它为一个函数对象。这时这个函数需要有且仅有这样一个参数,代表匹配结果的 Match
对象。因此可以将需要将要替换的字符分组,再用 Match
对象的 .group()
方法取出需要替换的字符串的组,并在函数内部进行运算。例如:
结果为:
这种使用函数来处理如何完成替换的方式非常灵活。
subn(pattern, repl, string, count=0, flags=0)
函数和 sub()
函数功能类似,唯一不同的是 subn()
函数的返回结果是一个包含 (result, count)
的元组,即可以同时获取正则替换的完整结果与替换次数。
分割
Python 内置了一个 .split()
方法,可以用来分割字符串:
但是该方法有一个缺点,那就是对于复杂情况下的分割效果不够理想:
上例中,
方法无法识别出多个空格,导致多个空格的分割出现了未达到预期的效果。.split()
这个时候,便可以使用 re
库中的分割函数 split(pattern, string, maxsplit=0, flags=0)
,该函数的效果是将 pattern
代表的正则表达式为分割标识,将 string
位于分割标识两侧的字符串拆分成列表。通俗地说,就是相比内置的 .split
方法,该函数可以用正则表达式作为分割标识了。因此相比内置的 .split()
方法,re
库中的 split()
函数分割更加的灵活。
例如,以上使用内置 .split()
分割失败的情况,可以使用 split()
函数这么处理:
可以看到正则表达式很完美地识别了多个空格并完成了拆分。
其它内容
正则表达式对象
当在 Python 中使用正则表达式进行匹配时,re
模块会执行两个步骤:
- 将输入的正则表达式编译成一个正则表达式对象。如果正则表达式的字符串本身不合法,会产生
re.error
错误。 - 用编译后的正则表达式去匹配字符串。
因此当要进行匹配大量数据时,需要重复使用一个正则表达式几百上千次。此时,出于效率的考虑,可以先预编译该正则表达式为一个正则表达式对象,再利用正则表达式对象的方法去匹配字符串,这样便省略了步骤 1 ,很好地提升了效率。
使用 re.compile(pattern, flags=0)
函数可以生成一个正则表达式对象:
结果为:
对于一个已经预编译完成的正则表达式对象,可以使用对象的一些方法来完成匹配。正则表达式对象方法有:
.match(string, pos, endpos)
.fullmatch(string, pos, endpos)
.search(string, pos, endpos)
.finall(string, pos, endpos)
.finditer(string, pos, endpos)
.sub(repl, string, count)
.subn(repl, string, count)
.split(string, maxsplit)
其中 pos
和 endpos
参数可以指定正则表达式的搜索位置。除此之外,这些方法与各自对应的函数的使用方法基本一致。
附录
参考资料
https://docs.python.org/3/library/re.html
Python3 re库官方文档