第10单元 使用正则表达式实现模式匹配
正则表达式是一种基于模式匹配的搜索、拆分及替换字符串的强大机制。re模块提供模式描述语言和用于匹配、搜索、拆分和替换字符串的函数。
从Python的角度来看,正则表达式只是一个包含模式描述的字符串。将一个需要多次使用的正则表达式编译为Pattern对象后,可以使模式匹配更加高效:
compiledPattern = re.compile(pattern, flags=0)
编译在不影响正确性的前提下,大大提高了模式匹配效率。可以根据需要,在编译时或之后执行时,设定模式匹配标志。最常见的标志是re.I(忽略字符大小写)和re.M(告诉re在多行模式下工作,并让运算符^和$也匹配行的开始或结束)。如果要组合使用多个匹配标志,只需直接添加即可。
理解正则表达式语言
部分正则表达式语言汇总在下表中。
表2 正则表达式语言
基本操作3 | |
. | 除换行符之外的任意字符 |
a | 字母a |
ab | 字符串ab |
x|y | x或y |
\y | 特殊字符y(例如^+{}$()[]|-?.*)的转义符 |
字符类 | |
[a-d] | a、b、c、d中的某个字符 |
[^a-d] | 除了a、b、c、d外的一个字符 |
\d | 一个数字字符 |
\D | 一个非数字字符 |
\s | 一个空白字符 |
\S | 一个非空白字符 |
\w | 一个字母数字字符4 |
\W | 一个非字母数字字符 |
数量相关 | |
x* | 0个或多个x |
x+ | 1个或多个x |
x? | 0个或1个x |
x{2} | 2个且仅2个x |
x{2,5} | 2至5个x |
转义字符 | |
\n | 换行符 |
\r | 回车符 |
\t | Tab |
定界符 | |
^ | 字符串起始处 |
\b | 单词边界 |
\B | 非单词边界 |
$ | 字符串结尾 |
组 | |
(x) | 捕获组 |
(?:x) | 非捕获组 |
3在Python 3.x中,字符串默认都是采用Unicode编码,这对\w、\W、\b、\B、\d、\D、\s和\S会有些影响。详情请参考Python官方手册(https://docs.python.org/3.3/library/re.html?Highlight=re#re.ASCII)。——译者注
4对于Python 3.x默认的Unicode编码,\w指的是一个Unicode字符;而对于ASCII编码,\w指任意一个字母或数字或下划线,即[a-zA-Z0-9_]。可以使用标志r.A、r.ASCII,或者在表达式之前添加(?a)来指定ASCII模式(https://docs.python.org/3.3/library/re.html?highlight=re)。——译者注
位于字符类表达式中间或结尾处的插入符(^)和连接符(-)不具有特殊含义,表示字符'^'和'-'。组可以改变操作的顺序。当成功匹配时,匹配捕获组的子字符串也包括在结果列表中。
注意,正则表达式要用到大量反斜杠('\'),而反斜杠又是Python中的转义字符。因此,如果要将反斜杠作为常规字符处理,必须再加一个反斜杠('\\'),这将导致正则表达式中出现大量的反斜杠,显得非常笨重。幸好Python支持原生字符串,在原生字符串中反斜杠不被解析为转义字符。
要定义原生字符串,只需要加一个r前缀。以下两个字符串是等价的,且两种表达式中都没有换行符:
"\\n" r"\n"
本书将使用原生字符串的方式来书写正则表达式。
接下来我们学习一些有用的正则表达式。使用这些例子无意吓唬你,但通过它们你应该认识到,相比于生活的不易,计算机科学无疑是一门更加困难的学科,而其中最难的部分就是模式匹配。
r"\w[-\w\.]*@\w[-\w]*(\.\w[-\w]*)+"
这是一个电子邮件地址。
r"<TAG\b[^>]*<(.*?)</TAG>"
这是一个具有结束标签的HTML标签。
r"[-+]?((\d*\.?\d+)|(\d\.))([eE][-+]?\d+)?"
这是一个浮点数。
至此,你也许会一时冲动,打算写出一个正则表达式来匹配有效的网址,这个任务听上去确实很吸引人,但却是极其困难的。建议你抑制住冲动,直接使用urllib.parse模块,相关的内容已在第9单元中介绍过了。
不规则正则表达式
Python正则表达式不是唯一可用的正则表达式。Perl语言使用的正则表达式也具有同样的处理能力,但是语法和Python不同,语义上也略有差异。在一些简单情况下(例如文件名匹配),可以使用glob模块,它是另一种类型的正则表达式(参见第11单元)。
使用模块re进行搜索、拆分和替换
一旦你编写并编译了一个正则表达式,就可以用它来拆分、匹配、搜索和替换子字符串。re模块提供了所有必要的函数,且大多数函数接受两种类型的模式:原生的和编译后的。
re.function(rawPattern, ...) compiledPattern.function(...)
函数split(pattern, string, maxsplit=0, flags=0)通过pattern将字符串拆分为至多maxsplit个子字符串,并返回子字符串列表(如果maxsplit==0,则返回所有子字符串)。如果你在寻找针对文本分析的分词器,那么这个函数可以作为一个免费的选择。
re.split(r"\W", "Hello, world!") ➾ ['Hello', '', 'world', ''] # 合并所有相邻的非字母字符 re.split(r"\W+", "Hello, world!") ➾ ['Hello', 'world', '']
函数match(pattern, string, flags=0)检查字符串的开头是否与正则表达式pattern相匹配。如果匹配成功,则该函数返回一个match对象,否则返回None。匹配对象(如果存在)可以使用start()、end()和group()函数,分别返回匹配片段的开始索引、结束索引以及片段本身。
mo = re.match(r"\d+", "067 Starts with a number") ➾ <sre.SREMatch object; span=(0, 3), match='067'> mo.group() ➾ '067' re.match(r"\d+", "Does not start with a number") ➾ None
函数search(pattern, string, flags=0)检查整个字符串是否存在匹配正则表达式的部分。如果匹配成功,则该函数返回一个match对象,否则返回None。如果想要匹配的片段不在字符串的开头,就应使用search()函数替换上述的match()函数。
re.search(r"[a-z]+", "0010010 Has at least one 010 letter 0010010", re.I) ➾ <_sre.SRE_Match object; span=(8, 11), match='Has'> # 区分大小写的方式 re.search(r"[a-z]+", "0010010 Has at least one 010 letter 0010010") ➾ <_sre.SRE_Match object; span=(9, 11), match='as'>
函数findall(pattern, string, flags=0)查找与正则表达式匹配的所有子字符串。该函数返回一个子字符串列表(当然,该列表可能为空)。
re.findall(r"[a-z]+", "0010010 Has at least one 010 letter 0010010", re.I) ➾ ['Has', 'at', 'least', 'one', 'letter']
捕获组与非捕获组
非捕获组作为正则表达式的一部分,在re模块中被视为一种简单的记号5。非捕获组使用的括号与算术表达式中的括号在作用上并无差异。例如,r"cab+"匹配以"ca"开头、后跟至少一个"b"的子字符串,而r"c(?:ab)"匹配以"c"开头、后跟一个或多个"ab"的子字符串。注意,正则表达式中"(?:"和其他部分之间没有空格。
与非捕获组不同,捕获组除了具有分组的作用,还能捕获匹配的子字符串。例如,捕获组r"c(ab)+"在search()函数作用下,返回在"c"及其后至多个"ab"的子字符串,而在findall()函数作用下,返回一系列"ab"子字符串6。
5顾名思义,非捕获组是一种不具备“捕获”能力的组。换句话说,除了不能从组中获得匹配的内容之外,非捕获组跟正常的(捕获)组没有什么区别。——译者注
6此处进一步给出一个例子:re.search(r"c(ab)+","cababjcabk")➾'cabab' re.findall(r"c(ab)+","cababjcabk")➾['ab','ab']。 ——译者注
函数sub(pattern, repl, string, flags=0)用repl替换字符串的所有非重叠匹配部分。使用可选参数count,可以限制替换的次数。
re.sub(r"[a-z ]+", "[...]", "0010010 has at least one 010 letter 0010010") ➾ '0010010[...]010[...]0010010'
正则表达式非常强大,可是在许多情况下(例如,通过文件的后缀匹配文件名),使用正则表达式有点“杀鸡用牛刀”了。实际上,可以通过globbing实现类似的效果,具体内容请参考下一单元。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论