返回介绍

第10单元 使用正则表达式实现模式匹配

发布于 2024-01-28 22:01:16 字数 6369 浏览 0 评论 0 收藏 0

正则表达式是一种基于模式匹配的搜索、拆分及替换字符串的强大机制。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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文