正则表达式简介

正则表达式(称为RE, 或正则, 或正则表达式模式)本质上是嵌入在 Python 中的一种微小的, 高度专业化的编程语言, 可通过 re 模块获得. 使用这种小语言, 你可以为要匹配的可能字符串集指定规则;此集可能包含英语句子, 电子邮件地址, TeX 命令或你喜欢的任何内容. 然后, 您可以询问诸如”此字符串是否与模式匹配?”或”此字符串中的模式是否匹配?”等问题. 你还可以使用正则修改字符串或以各种方式将其拆分.

正则表达式模式被编译成一系列字节码, 然后由用 C 编写的匹配引擎执行. 对于高级用途, 可能需要特别注意引擎如何执行给定的正则, 并将正则写入以某种方式生成运行速度更快的字节码.
正则表达式语言相对较小且受限制, 因此并非所有可能的字符串处理任务都可以使用正则表达式完成. 还有一些任务可以用正则表达式完成, 但表达式变得非常复杂. 在这些情况下, 你最好编写 Python 代码来进行处理; 虽然 Python 代码比精心设计的正则表达式慢, 但它也可能更容易理解.

正则表达式

匹配单字符

大多数字母和字符只会匹配自己. 例如 test 只会匹配字符串test
一些字符是特殊的, 并不会匹配自己. 如: . ^ $ * + ? { } [ ] \ | ( )等
给出两个字符,并用’-‘标记将他们分开即表示一系列字符.例如 [abc] 等同于 [a-c]. 而 [/u4e00-/u9fa5] 表示所有中文字符.
而有一些特殊的符号序列, 可以表示特殊的单字符含义:

序列 描述
. 匹配任意1个字符(除了\n)
[ ] 匹配[ ]中列举的字符
[^ ] 匹配[ ]中列举的字符之外的一个字符
\d 匹配数字,即0-9
\D 匹配非数字,即不是数字
\s 匹配空白,即 空格,tab键
\S 匹配非空白
\w 匹配非特殊字符,即a-z、A-Z、0-9、_、汉字
\W 匹配特殊字符,即非字母、非数字、非汉字、非_

匹配边界

有一些特殊的符号序列, 可以表示匹配的位置:

序列 描述
^ 匹配字符串开头
$ 匹配字符串末尾
\A 仅匹配字符串开头
\Z 仅匹配字符串末尾
\b 匹配\w和\W之间
\B [^\b]

匹配数量

能够匹配不同的字符集合是正则表达式可以做的第一件事.这对于字符串可用方法来说是不可能的. 但是, 如果这是正则表达式的唯一额外功能,那么它们就不会有太大的优势. 另一个功能是你可以指定正则的某些部分必须重复一定次数:

序列 描述
* 匹配前一个字符出现0次多次或者无限次,可有可无,可多可少
+ 匹配前一个字符出现1次多次或则无限次,直到出现一次
? 匹配前一个字符出现1次或者0次,要么有1次,要么没有
{m} 匹配前一个字符出现m次
{m,} 匹配前一个字符至少出现m次
{m,n} 匹配前一个字符出现m到n次

匹配分组

可以使用匹配分组实现同时匹配多种正则:

序列 描述
| 匹配左右任意一个表达式
(ab) 将括号中字符作为一个分组
\num 引用分组num匹配到的字符串
(?P<name>) 分组起别名
(?P=name) 引用别名为name分组匹配到的字符串

捕获组: 用数字引用的组.
子组从左到右编号,从 1 向上编号. 组可以嵌套; 要确定编号, 只需计算从左到右的左括号字符. 而\num是匹配第num组已匹配到的内容.
例如:

1
2
3
4
5
6
7
8
>>> p = re.compile(r'(a(b)c)d\1')
>>> m = p.match('abcdabc')
>>> m.group(0)
'abcdabc'
>>> m.group(1)
'abc'
>>> m.group(2)
'b'

命名组:不是通过数字引用它们,而是可以通过名称引用组.
命名组的语法是 Python 特定的扩展之一: (?P<name>…). name 显然是该组的名称.命名组将名称与组关联. 处理捕获组的匹配对象方法都接受按编号引用组的整数或包含所需组名的字符串. 命名组仍然是给定的数字, 因此你可以通过两种方式检索有关组的信息:

1
2
3
4
5
6
>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'

命名组很有用, 因为它们允许你使用容易记住的名称, 而不必记住数字.
表达式中的后向引用语法, 例如(…)\1,指的是组的编号. 当然有一种变体使用组名而不是数字. 这是另一个Python 扩展: (?P=name) 表示在当前点再次匹配名为name 的组的内容. 用于查找双字的正则表达式, \b(\w+)\s+\1\b 也可以写为 \b(?P<word>\w+)\s+(?P=word)\b

1
2
3
>>> p = re.compile(r'\b(?P<word>\w+)\s+(?P=word)\b')
>>> p.search('Paris in the the spring').group()
'the the'

注意:
\num 匹配的时num组中匹配到的内容, 而不是正则表达式, (?P=name) 也是同理, 匹配组名name的组中匹配到的内容

RE库的基本使用

RE库的基本函数

RE库的基本函数常用的有:

方法 描述
re.compile(pattern, flags=0) 将正则表达式的样式编译为一个正则表达式对象,可以用于匹配.
re.match(pattern, string, flags=0) 如果 string 开始的 0 或者多个字符匹配到了正则表达式样式, 就返回一个相应的匹配对象. 如果没有匹配, 就返回None
re.search(pattern, string, flags=0) 扫描整个字符串找到匹配样式的第一个位置,并返回一个相应的匹配对象。如果没有匹配,就返回None
re.findall(pattern, string, flags=0) 对 string 返回一个不重复的 pattern 的匹配列表, string 从左到右进行扫描, 匹配按找到的顺序返回. 如果样式里存在一到多个组, 就返回一个组合列表.空匹配也会包含在结果里
re.finditer(pattern, string, flags=0) pattern 在 string 里所有的非重复匹配, 返回为一个迭代 iterator 保存了匹配对象. string 从左到右扫描, 匹配按顺序排列. 空匹配也包含在结果里
re.split(pattern, string, maxsplit=0, flags=0) 用pattern 分开string 。如果在 pattern 中捕获到括号,那么所有的组里的文字也会包含在列表里。如果 maxsplit 非零,最多进行 maxsplit 次分隔,剩下的字符全部返回到列表的最后一个元素。
re.sub(pattern, repl, string, count=0, flags=0) 返回通过使用 repl 替换在 string 最左边非重叠出现的 pattern 而获得的字符串. 如果样式没有找到, 则返回原string。 repl 可以是字符串或函数; 如为字符串, 则其中任何反斜杠转义序列都会被处理.如果 repl 是一个函数, 那它会对每个非重复的 pattern 的情况调用. 可选参数 count 是要替换的最大次数, 0 为全部替换

其中 flags 为正则表达式使用时的控制标记 常用的有以下几种:

常用标记 描述
re.I re.IGNORECASE 进行忽略大小写匹配; 表达式如[A-Z] 也会匹配小写字符.
re.M re.MULTILINE 设置以后,样式字符 ‘^’ 匹配字符串的开始, 和每一行的开始(换行符后面紧跟的符号); 样式字符 ‘$‘ 匹配字符串尾, 和每一行的结尾(换行符前面那个符号). 默认情况下, ‘^’ 匹配字符串头, ‘$‘匹配字符串尾.
re.S re.DOTALL 让 ‘.’ 特殊字符匹配任何字符, 包括换行;如果没有这个标记, ‘.’ 就匹配除了换行符的其他任意字符。对应内联标记.
re.X re.VERBOSE 这个标记允许你编写更具可读性更友好的正则表达式. 通过分段和添加注释. 空白符号会被忽略, 当一个行内有 # 不在字符集和转义序列,那么它之后的所有字符都是注释。

反斜杠灾难

如前所述, 正则表达式使用反斜杠字符(‘') 来表示特殊形式或允许使用特殊字符而不调用它们的特殊含义. 这与 Python 在字符串文字中用于相同目的的相同字符的使用相冲突.
假设你想要编写一个与字符串 \section 相匹配的正则, 它可以在 LaTeX 文件中找到. 要找出在程序代码中写入的内容, 请从要匹配的字符串开始. 接下来, 您必须通过在反斜杠前面添加反斜杠和其他元字符, 从而产生字符串\section. 必须传递给 re.compile() 的结果字符串必须是\section. 但是, 要将其表示为 Python 字符串文字, 必须再次转义两个反斜杠. 字符阶段 \section 被匹配的字符串 \section 为 re.compile() 转义的反斜杠 “\\section” 为字符串字面转义的反斜杠.
简而言之, 要匹配文字反斜杠, 必须将 ‘\\’ 写为正则字符串, 因为正则表达式必须是 , 并且每个反斜杠必须表示为 \ 在常规 Python 字符串字面中. 在反复使用反斜杠的正则中, 这会导致大量重复的反斜杠, 并使得生成的字符串难以理解.
解决方案是使用 Python 的原始字符串表示法来表示正则表达式; 反斜杠不以任何特殊的方式处理前缀为 ‘r’ 的字符串字面, 因此 r”\n” 是一个包含 ‘' 和 ‘n’ 的双字符字符串, 而 “\n” 是一个包含换行符的单字符字符串.

下面是匹配邮箱的代码:

1
2
3
4
5
6
7
8
import re
s = input()
p = re.compile(r"[a-zA-Z0-9\u4e00-\u9fa5_-]+@[a-zA-Z0-9_-]+[\.a-zA-Z0-9_-]+")
m = re.match(p, s)
if(m):
print("匹配成功\n")
else:
print("匹配失败\n")

RE库Match对象

match()和search()函数会生成一个Match对象, Match对象有以下常用属性和方法:

属性 描述
Match.string 待匹配的文本
Match.re 匹配时使用的pattern对象
Match.pos 匹配开始搜索的位置
Match.endpos 匹配结束搜索的位置
方法 描述
Match.group() 返回正则匹配的字符串
Match.start() 返回匹配开始的位置
Match.end() 返回匹配结束的位置
Match.span() 返回包含匹配(start, end)位置的元组

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> import re
>>> m = re.search('[1-9]+', 'dw213wqe')
>>> m.string
'dw213wqe'
>>> m.re
re.compile('[1-9]+')
>>> m.pos
0
>>> m.endpos
8
>>> m.group()
'213'
>>> m.span()
(2, 5)

RE库的贪婪匹配和最小匹配

贪婪匹配

RE库默认采用贪婪匹配, 即输出匹配最长的子串, 所以, 在上一个例子中, m.group() 返回的子串为 ‘213’ , 而不是 ‘2’.

最小匹配

经过使用最小匹配操作符, 可以使RE库采用最小匹配, 最小匹配操作符为 ‘?’ , 主要有如下几种形式:

操作符 描述
*? 前一个字符0次或无限次扩展, 最小匹配
+? 前一个字符1次或无限次扩展, 最小匹配
?? 前一个字符0次或1次扩展, 最小匹配
{m, n}? 前一个字符m次至n次(含n)扩展, 最小匹配

例如:

1
2
3
4
>>> import re
>>> m = re.search('[1-9]+?', 'dw213wqe')
>>> m.group()
'2'