12.正则表达式 ★★★
1 概述
正则表达式,Regular Expression,缩写为 regex、regexp、RE 等。
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑(可以用来做检索,截取或者替换操作)。
正则表达式是文本处理极为重要的技术,用它可以对字符串按照某种规则进行检索、替换。
1970 年代,Unix 之父 Ken Thompson 将正则表达式引入到 Unix 中文本编辑器 ed 和 grep 命令中,由此正则表达式普及开来。
1980 年后,perl 语言对 Henry Spencer 编写的库,扩展了很多新的特性。1997 年开始,Philip Hazel 开发出了 PCRE(Perl Compatible Regular Expressions),它被 PHP 和 HTTPD 等工具采用。
正则表达式应用极其广泛,shell 中处理文本的命令、各种高级编程语言都支持正则表达式。
作用:
- 给定的字符串是否符合正则表达式的过滤逻辑(称作“匹配”)。
- 可以通过正则表达式,从字符串中获取我们想要的特定部分。
- 还可以对目标字符串进行替换操作。
2 分类
-
BRE
基本正则表达式,grep、sed、vi 等软件支持。vim 有扩展。
-
ERE
扩展正则表达式,egrep(grep-E)、sed-r 等。
-
PCRE
几乎所有高级语言都是 PCRE 的方言或者变种。Python 从 1.6 开始使用 SRE 正则表达式引擎,可以认为是 PCRE 的子集,见模块 re。
3 基本语法
3.1 元字符
metacharacter
| 代码 | 说明 | 举例 |
|---|---|---|
. |
匹配换行符(\n)以外的任意一个字符 |
. |
[abc] |
字符集合,只能表示一个字符位置。 匹配所包含的任意一个字符。 |
[abc] 匹配 "plain" 中 'a' |
[^abc] |
字符集合,只能表示一个字符位置。 匹配除去集合内字符的任意一个字符 |
[^abc] 可以匹配 "plain" 中的 'p'、'l'、'i' 或者 'n' |
[a-z] |
字符范围,也是个集合,表示一个字符位置 匹配所包含的任意一个字符 |
常用 [A-Z] [0-9] |
[^a-z] |
字符范围,也是个集合,表示一个字符位置 匹配除去集合内字符的任意一个字符 |
|
\b |
匹配一个单词边界,也就是指单词和空格间的位置 | er\b 可以匹配以 "er" 结尾的单词中的 "er"\bab 可以匹配以 "ab" 开头的单词中的 "ab" |
\B |
匹配非单词边界 | t\B 匹配包含 't' 的单词但是不以 't' 结尾的单词中的 't',例如 "write" 中的 't'\Bb 不以 'b' 开头的含有 'b' 的单词,例如 "able" 中的 'b' |
\d |
匹配一个数字字符。等价于 [0-9] |
|
\D |
匹配一个非数字字符。等价于 [^0-9] |
|
\s |
匹配一个任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v] |
|
\S |
匹配一个任何非空白字符。等价于 [^ \f\n\r\t\v] |
|
\w |
匹配一个字母、数字、下划线,包括汉字。等价于 [A-Za-z0-9_] + 所有汉字 |
Python 等方言中 \w 可以匹配中文PCRE(PHP) 等方言中 \w 无法匹配中文使用时注意看清 re 使用的规则 |
\W |
匹配一个非字母、数字、下划线及汉字 |
示例:元字符的使用
原始文本:
hello world
小龙女
正则表达式:
.
匹配结果:
h
e
l
l
o
w
o
r
l
d
小
龙
女
原始文本:
hello world
小龙女
正则表达式:(使用 Python 方言)

\w
匹配结果:
h
e
l
l
o
w
o
r
l
d
小
龙
女
原始文本:
hello world
小龙女
正则表达式:(使用 Python 方言)
\w\w
匹配结果:
he
ll
wo
rl
小龙
原始文本:
hello world
123
小龙女
正则表达式:
\d
匹配结果:
1
2
3
原始文本:
hello world
123
小龙女
正则表达式:
[heo ]
匹配结果:
h
e
o
o
原始文本:
hello world
123 4
小龙女
正则表达式:
[heo\s]
匹配结果:

原始文本:
hello world
123 4
小龙女
正则表达式:
lo\s
匹配结果:
lo
原始文本:
hello world
123 4
小龙女
正则表达式:
\d\s\d
匹配结果:
3 4
原始文本:
hello world
123 4
小龙女
正则表达式:
[\d\s]
匹配结果:

原始文本:
hello world
12 34
小龙女
正则表达式:
\w\s\w
匹配结果:

原始文本:
hello world
12 34
小龙女
正则表达式:
\w\s\d
匹配结果:

原始文本:
abc xyz
12 34
中文.
正则表达式:
\b\w
匹配结果:

原始文本:
abc xyz
12 34
中文.
正则表达式:
\b\w\w
匹配结果:

原始文本:
abc xyz
12 34
中文.
正则表达式:
\b\w\w\w
匹配结果:

原始文本:
abc xyz
12 34
中文.
正则表达式:
\b\w\w\b
匹配结果:

3.2 转义
凡是在正则表达式中有特殊意义的符号,如果想使用它的本意,请使用 \ 转义。反斜杠自身,得使用 \\。
\r、\n 还是转义后代表回车、换行。
3.3 重复(限定符)
如果要匹配手机号码,需要形如 \d\d\d\d\d\d\d\d\d\d\d 这样的正则表达式。其中 \d 出现了 11 次,表达方式烦琐。正则表达式作为一门小型的语言,还提供了对表达式的一部分进行重复处理的功能。例如,* 可以对正则表达式的某个部分重复匹配多次。这种匹配符号称为限定符。
| 代码 | 说明 | 举例 |
|---|---|---|
* |
匹配前面的子表达式零次或多次 | 例如,zo* 能匹配以 'z' 开头的后跟任意个 'o' 的字符。 |
+ |
匹配前面的子表达式一次或多次 | 例如,zo+ 能匹配以 'z' 开头的后跟至少一个 'o' 的字符。 |
? |
匹配前面的子表达式零次或一次 | do? 可以匹配 'd' 或 'do'。 |
{n} |
n 是一个非负整数。匹配确定的 n 次 | o{2} 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 'o'。 |
{n,} |
n 是一个非负整数。至少匹配 n 次 | o{2,} 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 'o'。o{1,} 等价于 o+。o{0,} 则等价于 o*。 |
{n,m} |
m 和 n 均为非负整数,其中 n <= m。 最少匹配 n 次且最多匹配 m 次 |
o{1,3} 将匹配 "fooooood" 中的前三个 'o'。o{0,1} 等价于 o?。请注意在逗号和两个数之间不能有空格。 |
示例:限定符的使用
原始文本:
abc xyz
12 34
中文好的.abc
正则表达式:
\w+
匹配结果:

原始文本:
abc xyz
12 34
中文好的.abc
正则表达式:
\w*
匹配结果:

原始文本:
abc xyz
12 34
中文好的.abc
正则表达式:
\w?
匹配结果:

原始文本:
abc xyz m
12 34
中文好的.abc
正则表达式:
\w\w*
匹配结果:

原始文本:
abc xyz m
12 34
中文好的.abc
正则表达式:
\w\w+
匹配结果:

原始文本:
abc xyz m
12 34
中文好的.abc
正则表达式:
\w\w?
匹配结果:

原始文本:
abc xyz m
12 34
中文好的.ef
正则表达式:
\w\w*
匹配结果:

3.4 或
| 代码 | 说明 | 举例 |
|---|---|---|
x|y |
匹配 x 或者 y | "wood took foot food" 使用 wood|f 可以匹配到 'wood'、'f'、'f'。(w|f)ood 可以匹配到 'wood'、'food'。 |
示例:正则中的或
原始文本:
took food wood foot
正则表达式:
f|wood
匹配结果:

原始文本:
took food wood foot
正则表达式:
(f|w)ood
匹配结果:

原始文本:
took food wood foot
正则表达式:
(?:f|w)ood
匹配结果:括号只为了改变优先级,不进行分组

3.5 边界字符
| 代码 | 说明 | 举例 |
|---|---|---|
^ |
字符串的开头 | |
$ |
字符串的结尾 | |
\b |
匹配一个单词边界,也就是指单词和空格间的位置 | |
\B |
匹配非单词的边界 |
3.6 捕获(分组)
| 代码 | 说明 | 举例 |
|---|---|---|
(pattern) |
使用小括号指定一个子表达式,也叫分组。 捕获后会自动分配组号从 1 开始。 可以改变优先级。 匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在 VBScript 中使用 SubMatches 集合,在 JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 \( 或 \)。 |
|
\数字 |
匹配对应的分组 | (very) \1 可以从 "vary very very vary vary" 中匹配到 'very very',但是捕获的组 group 是 very |
(?:pattern) |
匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 " 或 " 字符 (|) 来组合一个模式的各个部分时很有用 如果仅仅为了改变优先级,就不需要捕获分组 |
(?:w|f)ood <==> wood|foodindustr(?:y|ies) 就是一个比 industry|industries 更简略的表达式 |
(?<name>exp)或 (?'name'exp) |
命名分组捕获,可以通过 name 访问分组Python 语法必须是 (?P<name>exp) |
如果一个模式字符串中有用一对圆括号括起来的部分,那么这部分就会作为一组。当然,如果模式字符串中没有任何用圆括号括起来的部分,那么就不会对待匹配的字符串进行分组。
示例:
原始文本:
took food wood foot
正则表达式:
(f|w)ood
匹配结果:

原始文本:
took food wood foot
正则表达式:
(?:f|w)ood
匹配结果:括号只为了改变优先级,不进行分组

原始文本:
took food wood foot
正则表达式:
(?:f|wood)
匹配结果:

原始文本:
took food wood foot
正则表达式:
((?:f|w)ood)
匹配结果:对 food 和 wood 进行分组

原始文本:
took food wood foot
正则表达式:对 f 和 w 进行分组
(?:(f|w)ood)
匹配结果:

原始文本:
took food wood foot
正则表达式:对 f 和 w 进行分组
(?:(f|w)(ood))
匹配结果:对 f、w 和 ood 进行分组

原始文本:
took food wood foot
正则表达式:对 f 和 w 进行分组
((f|w)(ood))
匹配结果:对 food、wood、f、w 和 ood 进行分组

原始文本:
very very vary
正则表达式:
v.*y
匹配结果:

原始文本:
very very vary
sorry
正则表达式:
v.*y
匹配结果:

原始文本:
so very very vary
sorry vys
正则表达式:
(v.+y)
匹配结果:

原始文本:
so very very vary
sorry vys
正则表达式:
(v.+y)
匹配结果:

原始文本:
so very very vary sorry vys
正则表达式:
(v.+y)
匹配结果:

原始文本:
so very very vary sorry vys
正则表达式:\1 引用前面的分组。在匹配时 \1 会被替换为第一个匹配的分组中的字符串。
(v.+y) \1
匹配结果:

原始文本:
so very very vary vary sorry vys
正则表达式:\1 引用前面的分组。在匹配时 \1 会被替换为第一个匹配的分组中的字符串。
(v.+y) \1
匹配结果:

原始文本:
so very very vary vary sorry vys
正则表达式:
(v).+(y)
匹配结果:

原始文本:
so very very vary vary sorry vys
正则表达式:
(v).+(y)\b
匹配结果:

原始文本:
so very very vary vary sorry vys
正则表达式:
(v).+(y)
匹配结果:

原始文本:
so very very vary vary sorry vys
正则表达式:
(v).+(y) \1\2
匹配结果:

原始文本:
so very very vary vary sorry vys
正则表达式:注意方言不要选 Python。
(?<abc>v.+y)
匹配结果:

3.7 断言
零宽断言
| 代码 | 说明 | 举例 |
|---|---|---|
(?=exp) |
零宽度正预测先行断言 断言 exp 一定在匹配的右边出现,也就是说断言后面一定跟个 exp |
f(?=oo) 'f' 后面一定有 'oo' 出现f(?=oo) 可以匹配 "food" 和 "foot" 中的 'f' |
(?<=exp) |
零宽度正回顾后发断言 断言 exp 一定出现在匹配的左边出现,也就是说前面一定有个 exp 前缀 |
(?<=f)ood、(?<=t)ook 分别匹配 'ood'、'ook' 前一定有 f、t 出现 |
示例:
原始文本:
wood took foot food
正则表达式:
f(?=oo)
匹配结果:

原始文本:
wood took foot food
正则表达式:
(?<=f)oo
匹配结果:

原始文本:
wood took foot food
正则表达式:
(?<=f|w)oo
匹配结果:

负向零宽断言
| 代码 | 说明 | 举例 |
|---|---|---|
(?!exp) |
零宽度负预测先行断言 断言 exp 一定不会出现在右侧,也就是说断言后面一定不是 exp |
\d{3}(?!\d) 匹配 3 位数字,断言 3 位数字后面一定不能是数字foo(?!d) 'foo' 后面一定不是 'd' |
(?<!exp) |
零宽度负回顾后发断言 断言 exp 一定不能出现在左侧,也就是说断言前面一定不能是 exp |
(?<!f)ood 'ood' 的左边一定不是 'f' |
| 代码 | 说明 | 举例 |
|---|---|---|
(?#comment) |
注释 | f(?=oo)(?#这是注释) |
分组和捕获是同一个意思。
示例:
原始文本:
wood took foot food
正则表达式:
(?<!f|w)oo
匹配结果:

原始文本:
wood took foot food feet
正则表达式:
f(?!oo)
匹配结果:

原始文本:
wood took foot food feet
正则表达式:这种注释方式并不好,不推荐使用
f(?!oo)(?#这是注释)
匹配结果:

3.8 贪婪与非贪婪
贪婪模式指数量词默认是贪婪的,总是尝试匹配尽可能多的字符。非贪婪模式与贪婪相反,总是尝试匹配尽可能少的字符,可以使用 *,?,+,{m,n} 后面加上 ?,使贪婪变成非贪婪。
默认是贪婪模式,也就是说尽量多匹配更长的字符串。
非贪婪很简单,在重复的符号是后面加上一个 问号(?) 是就尽量的少匹配了
| 代码 | 说明 | 举例 |
|---|---|---|
*? |
匹配任意次,但尽可能少重复 | |
+? |
匹配至少 1 次,但尽可能少重复 | |
?? |
匹配 0 次或 1 次,但尽可能少重复 | |
{n,}? |
匹配至少 n 次,但尽可能少重复 | |
{n,m}? |
匹配至少 n 次,至多 m 次,但尽可能少重复 |
示例:
原始文本:
so very very vary sorry vys
正则表达式:默认为贪婪模式
v.*y
匹配结果:

原始文本:
so very very vary sorry vys
正则表达式:非贪婪模式
v.*?y
匹配结果:

原始文本:
so very very vary sorry vys
正则表达式:非贪婪模式
v.+?y
匹配结果:

原始文本:
so very very vary sorry vys
正则表达式:非贪婪模式
v.??y
匹配结果:

原始文本:
so very very vary sorry vys vay
正则表达式:非贪婪模式
v.??y
匹配结果:

3.9 引擎选项
| 代码 | 说明 | python |
|---|---|---|
| IgnoreCase | 匹配时忽略大小写 | re.Ire.IGNORECASE |
| Singleline | 单行模式,. 可以匹配所有字符,包括换行符 \n |
re.Sre.DOTALL |
| Multiline | 多行模式,将 \n 看做换行符 |
re.Mre.MULTILINE |
| IgnorePatternWhitespace | 忽略表达式中的空白字符,如果要使用空白字符,用转义字符,# 可以用来做注释不推荐使用 |
re.Xre.VERBOSE |
-
默认模式:
可以看做待匹配的文本是一行,不能看做多行,
.点号不能匹配换行符,^和$表示行首和行尾,而行首行尾就是整个字符串的开头和结尾。 -
单行模式:
基本和默认模式一样,只是
.点号终于可以匹配任意一个字符包括换行符,这时所有文本就是一个长长的只有一行的字符串。^就是这一行字符串的行首,$就是这一行的行尾。.可以匹配所有字符,包括换行符。^表示整个字符串的开头,$表示整个字符串的结尾。 -
多行模式:
重新定义了行的概念,但不影响
.点号的行为,^和$还是行首行尾的意思,只不过因为多行模式可以识别换行符了。“结束” 指的是
\n前的字符,注意最后一行结尾可以没有\n.可以匹配除了换行符之外的字符,多行不影响.点号^表示行首,$行尾,只不过这里的行是每一个行
示例:
原始文本:
so very very vary
sorry vys
ray
正则表达式:
v.*y
匹配结果:

原始文本:
so very very sorry
vys ray
正则表达式:
v.*y
匹配结果:

原始文本:
so very very sorry
vys ray
正则表达式:单行模式
v.*y
匹配结果:

原始文本:
so very very sorry
vys ray
正则表达式:单行模式
^v.*y
匹配结果:

原始文本:
so very very sorry
vys ray
正则表达式:默认模式
^v.*y
匹配结果:

原始文本:
so very very sorry
vys ray test
正则表达式:多行模式
v.*y
匹配结果:

原始文本:
so very very sorry
vys ray test
正则表达式:多行模式
^v.*y
匹配结果:

原始文本:
so very very sorry
vys ray test
正则表达式:多行模式 + 单行模式
^v.*y
匹配结果:

原始文本:
so very very sorry
vys ray test
正则表达式:多行模式 + 单行模式
v.*y
匹配结果:

原始文本:
so very very sorry
vys ray test
正则表达式:单行模式
v.*y$
匹配结果:

原始文本:
so very very sorry
vys ray test
tiny
正则表达式:单行模式
v.*y$
匹配结果:

原始文本:
so very very sorry
vys ray test
tiny
正则表达式:单行模式
v.*y$
匹配结果:
4 Python 的正则表达式
python 使用 re 模块提供了正则表达式处理的能力
4.1 常量
| 常量 | 说明 |
|---|---|
| re.M re.MULTILINE |
多行模式 |
| re.S re.DOTALL |
单行模式 |
| re.I re.IGNORECASE |
忽略大小写 |
| re.X re.VERBOSE |
忽略表达式中的空白字符 |
使用 |(位或) 运算连接多个模式选择
4.2 编译——compile
re.compile(pattern, flags=0)
设定 flags,编译模式,返回正则表达式对象 regex 。
pattern 就是正则表达式字符串,flags 是引擎选项。正则表达式需要被编译,为了提高效率,这些编译后的结果被保存,下次使用同样的 pattern 的时候,就不需要再次编译。

re 的其它方法为了提高效率都调用了编译方法,就是为了提速。
4.3 单次匹配——match
re.match(pattern, string, flags=0)
regex.match(string[ , pos[, endpos]])
| 参数 | 描述 |
|---|---|
| pattern | 匹配的正则表达式 |
| string | 要匹配的字符串。 |
| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。如下表列出正则表达式修饰符——可选标志 |
正则表达式修饰符
| 修饰符 | 描述 |
|---|---|
| re.I | 使匹配对大小写不敏感 |
| re.L | 做本地化识别(locale-aware)匹配 |
| re.M | 多行匹配,影响 ^ 和 $ |
| re.S | 使 . 匹配包括换行在内的所有字符 |
| re.U | 根据 Unicode 字符集解析字符。这个标志影响 \w,\W,\b,\B。 |
| re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
re.match是用来进行正则匹配检查的方法,如果字符串开头的 0 个或多个字符匹配正则表达式模式,则返回相应的match对象。如果字符串不匹配模式,返回None(注意不是空字符串"")- 匹配对象
Match Object具有group()方法,用来返回字符串的匹配部分。具有span()方法,返回匹配字符串的位置(元组存储开始,结束位置)。具有start(),end()方法,存储匹配数据的开始和结束位置。(也可以通过对象的dir(对象查看对象的方法))
re.match 匹配必须从字符串的开头进行匹配。匹配成功返回 Match 实例对象。

- 匹配不上,返回 None
- 匹配上返回 Match 类型实例 res
- span (start, end),前包后不包
- match=xxx 匹配的结果
- res.start() 匹配结果的开始索引
- res.end() 匹配结果的结束索引
- res.string 源字符串,即在哪个字符串中匹配的
regex 对象 match 方法可以重新设定匹配的开始位置和结束位置。返回 Match 实例对象。

- 返回值同
re.match一样
示例:re.match 的使用
import re
s = """bottle\nbag\nbig\napple"""
# re.match 方法
# 匹配不上,返回 Noneres = re.match('quit', s, re.S)
print(type(res))
print(res)
print("=" * 20)
# 匹配上返回 Match 类型实例,span (start, end),match=xxx 匹配结果
res = re.match('b.+', s, re.S)
print(type(res))
print(res)
# 得到匹配结果的索引值
print(f"匹配结果的开始索引:{res.start()}")
print(f"匹配结果的结束索引:{res.end()}")
# 得到匹配结果
print(f"匹配结果:{s[res.start(): res.end()]}")
print("=" * 20)

示例:re.match 多行模式
import re
s = """bottle\nbag\nbig\napple"""
# re.match 方法
# 匹配上返回 Match 类型实例,span (start, end),match=xxx 匹配
res = re.match('b.+', s, re.M)
print(type(res))
print(res)
# 得到匹配结果的索引值
print(f"匹配结果的开始索引:{res.start()}")
print(f"匹配结果的结束索引:{res.end()}")
# 得到匹配结果
print(f"匹配结果:{s[res.start(): res.end()]}")
print("=" * 20)

示例:regex 对象的 match 方法
import re
s = """bottle\nbag\nbig\napple"""
regex = re.compile('b.+', re.M)
print(f"regex 对象类型:{type(regex)}")
print(f"regex:{regex}")
print("=" * 20)
# 匹配上返回 Match 类型实例,span (start, end),match=xxx 匹配结果
res = regex.match(s, 7)
# 源字符串
print(f"源字符串:{res.string}")
print(f"res 的类型:{type(res)}")
print(f"res:{res}")

match 方法匹配方式详解
先看以下的一个案例:四次匹配分别返回什么?
import re
s = """bottle\nbag\nbig\napple"""
for i, c in enumerate(s, 1):
print((i - 1, c), end="\n" if i % 10 == 0 else " ")
res = re.match('a', s)
print(res)
res = re.match('^a', s)
print(res)
res = re.match('a', s, re.M)
print(res)
res = re.match('^a', s, re.M)
print(res)
结果:
import re
s = """bottle\nbag\nbig\napple"""
for i, c in enumerate(s, 1):
print((i - 1, c), end="\n" if i % 10 == 0 else " ")
# match 是单次匹配,并不是全文搜索,而且要求必须是从头匹配,从索引 0 开始
res = re.match('a', s)
print(res) # None
res = re.match('^a', s)
print(res) # None 默认模式,一整行,没有 a 开头。
res = re.match('a', s, re.M)
print(res) # None
res = re.match('^a', s, re.M)
print(res) # None

分析:
match 是单次匹配,并不是全文搜索,而且要求必须是从头匹配,从索引 0 开始。
re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match() 就返回 None。
re.match('a', s) 和 re.match('a', s, re.M) 相当于匹配时判断字符串 s 的第一个字符是 'a' 吗?不是,直接返回 None,不向后匹配。'^a' 开头同理。
regex.match() 方法也是同理。但是 regex.match() 可以改变字符串匹配的起始位置。
import re
s = """bottle\nbag\nbig\napple"""
for i, c in enumerate(s, 1):
print((i - 1, c), end="\n" if i % 10 == 0 else " ")
regex = re.compile('b')
print(regex.match(s)) # b 0
regex = re.compile('^b')
print(regex.match(s)) # b 0
regex = re.compile('^a')
# 字符串 s 是以 'a' 开头的吗?
print(regex.match(s)) # None
regex = re.compile('^a', re.M)
# match 方法从字符串开始位置进行匹配,如果第一个字符不匹配,直接返回 None。
# 0 索引不是 'a' ,直接返回 None,不向后进行匹配。
print(regex.match(s)) # None
regex = re.compile('^a', re.M)
# 虽然是多行模式,也是从索引 8 进行匹配,但是索引 8 的 'a' 不是开头
print(regex.match(s, 8)) # None
regex = re.compile('a', re.M)
print(regex.match(s, 8)) # a 8
regex = re.compile('^a', re.M)
# 从索引 15 进行匹配,索引 15 的 'a' 是开头
print(regex.match(s, 15)) # a 15
regex = re.compile('^a')
# 默认模式,行首和行尾就是字符串的开头和结尾
print(regex.match(s, 15)) # a 15

4.4 单次匹配——search
search 方法在一个字符串中搜索满足文本模式的字符串。语法格式如下:
re.search(pattern, string, flags=0)
regex.search(string[ , pos[, endpos]])
函数参数与 match 方法类似
| 参数 | 描述 |
|---|---|
| pattern | 匹配的正则表达式 |
| string | 要匹配的字符串。 |
| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。如下表列出正则表达式修饰符——可选标志 |
正则表达式修饰符
| 修饰符 | 描述 |
|---|---|
| re.I | 使匹配对大小写不敏感 |
| re.L | 做本地化识别(locale-aware)匹配 |
| re.M | 多行匹配,影响 ^ 和 $ |
| re.S | 使 . 匹配包括换行在内的所有字符 |
| re.U | 根据 Unicode 字符集解析字符。这个标志影响 \w,\W,\b,\B。 |
| re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
re.search 匹配在整个字符串中进行匹配。匹配成功返回 Match 实例对象;匹配失败返回 None。

regex 对象 search 方法可以重新设定匹配的开始位置和结束位置。匹配成功返回 Match 实例对象;匹配失败返回 None。
match 与 search 的区别
re.match 只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回 None;而 re.search 匹配整个字符串,直到找到一个匹配。
search 方法示例
import re
s = """bottle\nbag\nbig\napple"""
for i, c in enumerate(s, 1):
print((i - 1, c), end="\n" if i % 10 == 0 else " ")
res = re.search('b', s)
print(type(res))
print(res) # b 0 返回一个 Match 示例
res = re.match('a', s)
print(res) # None
res = re.search('a', s)
print(res) # a 8
res = re.search('a', s, re.M)
# 多行模式只改变 ^ 和 $ 的行为
print(res) # a 8
res = re.search('^a', s, re.M)
print(res) # a 15
res = re.search('^a', s)
print(res) # None

import re
s = """bottle\nbag\nbig\napple"""
for i, c in enumerate(s, 1):
print((i - 1, c), end="\n" if i % 10 == 0 else " ")
regex = re.compile('^a')
# 默认模式的行首和行尾就是字符串的开头和结尾
print(regex.search(s)) # None
regex = re.compile('^a')
# 默认模式的行首和行尾就是字符串的开头和结尾
print(regex.search(s, 15)) # None
regex = re.compile('^a', re.M)
print(regex.search(s, 15)) # a 15

4.5 单次匹配——fullmatch
fullmatch 是单次匹配,要求指定区间全部匹配。语法格式如下:
re.search(pattern, string, flags=0)
regex.search(string[ , pos[, endpos]])
函数参数与 match 方法类似
| 参数 | 描述 |
|---|---|
| pattern | 匹配的正则表达式 |
| string | 要匹配的字符串。 |
| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。如下表列出正则表达式修饰符——可选标志 |
正则表达式修饰符
| 修饰符 | 描述 |
|---|---|
| re.I | 使匹配对大小写不敏感 |
| re.L | 做本地化识别(locale-aware)匹配 |
| re.M | 多行匹配,影响 ^ 和 $ |
| re.S | 使 . 匹配包括换行在内的所有字符 |
| re.U | 根据 Unicode 字符集解析字符。这个标志影响 \w,\W,\b,\B。 |
| re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
re.fullmatch 在字符串的指定区间中进行匹配。匹配成功返回 Match 实例对象;匹配失败返回 None。

regex 对象 fullmatch 方法可以重新设定匹配的开始位置和结束位置。匹配成功返回 Match 实例对象;匹配失败返回 None。
fullmatch 方法示例
import re
s = """bottle\nbag\nbig\napple"""
for i, c in enumerate(s, 1):
print((i - 1, c), end="\n" if i % 10 == 0 else " ")
res = re.fullmatch('b.+', s)
print(type(res), res)
res = re.fullmatch('bag', s)
print(type(res), res)
res = re.fullmatch('b.+', s, re.S)
print(type(res), res) # 0-20 全匹配
regex = re.compile('bag')
print(regex.fullmatch(s, 7)) # None
regex = re.compile('bag')
print(regex.fullmatch(s, 7, 11)) # None
regex = re.compile('bag')
# 区间完全匹配。前包后不包
print(regex.fullmatch(s, 7, 10)) # 7-10 bag
regex = re.compile('bag')
# 前包后不包
print(regex.fullmatch(s, 7, 9)) # None
regex = re.compile('^bag')
# 默认模式行首和行尾就是字符串的开头和结尾
print(regex.fullmatch(s, 7, 10)) # None
regex = re.compile('^bag', re.M)
# 默认模式行首和行尾就是字符串的开头和结尾
print(regex.fullmatch(s, 7, 10)) # 7-10 bag

4.6 全文搜索——findall
findall 是全文搜索,在字符串中找到正则表达式所匹配的所有子串。并把它们作为一个列表返回。语法格式如下:
re.findall(pattern, string, flags=0)
regex.findall(string[ , pos[, endpos]])
函数参数与 match 方法类似
| 参数 | 描述 |
|---|---|
| pattern | 匹配的正则表达式 |
| string | 要匹配的字符串。 |
| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。如下表列出正则表达式修饰符——可选标志 |
正则表达式修饰符
| 修饰符 | 描述 |
|---|---|
| re.I | 使匹配对大小写不敏感 |
| re.L | 做本地化识别(locale-aware)匹配 |
| re.M | 多行匹配,影响 ^ 和 $ |
| re.S | 使 . 匹配包括换行在内的所有字符 |
| re.U | 根据 Unicode 字符集解析字符。这个标志影响 \w,\W,\b,\B。 |
| re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
re.findall 对整个字符串,从左至右匹配。匹配成功返回所有匹配项的列表;匹配失败返回空列表 []。

regex 对象 findall 方法可以重新设定匹配的开始位置和结束位置。匹配成功返回所有匹配项的列表;匹配失败返回空列表 []。
findall 方法示例
import re
s = """bottle\nbag\nbig\napple"""
for i, c in enumerate(s, 1):
print((i - 1, c), end="\n" if i % 10 == 0 else " ")
res = re.findall('b', s)
print(type(res), res) # <class 'list'> ['b', 'b', 'b']
res = re.findall(r'b\w+', s)
print(type(res), res) # <class 'list'> ['bottle', 'bag', 'big']
regex = re.compile(r'b\w+')
print(regex.findall(s)) # ['bottle', 'bag', 'big']
regex = re.compile(r'b\w+')
print(regex.findall(s, 1)) # ['bag', 'big']
regex = re.compile(r'^b\w+')
# 默认模式行首和行尾就是字符串的开头和结尾
print(regex.findall(s)) # ['bottle']
regex = re.compile(r'^b\w+')
print(regex.findall(s, 1)) # []
regex = re.compile(r'^b\w+', re.M)
print(regex.findall(s)) # ['bottle', 'bag', 'big']

4.7 全文搜索——finditer
finditer 在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。语法格式如下:
re.finditer(pattern, string, flags=0)
regex.finditer(string[ , pos[, endpos]])
函数参数与 match 方法类似
| 参数 | 描述 |
|---|---|
| pattern | 匹配的正则表达式 |
| string | 要匹配的字符串。 |
| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。如下表列出正则表达式修饰符——可选标志 |
正则表达式修饰符
| 修饰符 | 描述 |
|---|---|
| re.I | 使匹配对大小写不敏感 |
| re.L | 做本地化识别(locale-aware)匹配 |
| re.M | 多行匹配,影响 ^ 和 $ |
| re.S | 使 . 匹配包括换行在内的所有字符 |
| re.U | 根据 Unicode 字符集解析字符。这个标志影响 \w,\W,\b,\B。 |
| re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
re.finditer 对整个字符串,从左至右匹配,返回所有匹配项。匹配成功返回迭代器;匹配失败返回一个空迭代器。

regex 对象 finditer 方法可以重新设定匹配的开始位置和结束位置。匹配成功返回迭代器;匹配失败返回一个空迭代器。
finditer 方法示例
import re
s = """bottle\nbag\nbig\napple"""
for i, c in enumerate(s, 1):
print((i - 1, c), end="\n" if i % 10 == 0 else " ")
res = re.finditer(r'b\w+', s)
print(type(res), res)
for i in res:
print(type(i), i)
print("=" * 40)
regex = re.compile(r'^b')
res = regex.finditer(s)
print(type(res), res)
for i in res:
print(type(i), i)
print("=" * 40)
regex = re.compile(r'^b', re.M)
for i in regex.finditer(s):
print(i)

4.8 匹配替换——sub
sub 在字符串中找到正则表达式所匹配的所有子串,并把它们替换为指定的内容,返回一个替换后的字符串。语法格式如下:
re.sub(pattern, replacement, string, count=0, flags=0)
regex.sub(replacement, string, count=0)
函数参数
| 参数 | 描述 |
|---|---|
| pattern | 匹配的正则表达式 |
| replacement | 用于替换的文本 |
| string | 要匹配的字符串 |
| count | 至多替换的次数,count=1 至多替换一次。默认为 0,不限制次数。 |
| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。如下表列出正则表达式修饰符——可选标志 |
正则表达式修饰符
| 修饰符 | 描述 |
|---|---|
| re.I | 使匹配对大小写不敏感 |
| re.L | 做本地化识别(locale-aware)匹配 |
| re.M | 多行匹配,影响 ^ 和 $ |
| re.S | 使 . 匹配包括换行在内的所有字符 |
| re.U | 根据 Unicode 字符集解析字符。这个标志影响 \w,\W,\b,\B。 |
| re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
re.sub 使用 pattern 对字符串 string 进行匹配,对匹配项使用 repl 替换,返回一个替换后的字符串。
replacement 可以是 string、bytes、function。
sub 方法示例
import re
s = """bottle\nbag\nbig\napple"""
res = re.sub(r'b\w+', 'xyz', s)
print(type(res), res.encode())
# 指定至多替换次数
res = re.sub(r'b\w+', 'xyz', s, count=1)
print(type(res), res.encode())
regex = re.compile(r'b\w+')
res = regex.sub('xyz', s)
print(type(res), res.encode())
regex = re.compile(r'b\w+')
res = regex.sub('xyz', s, count=1)
print(type(res), res.encode())

4.9 匹配替换——subn
finditer 在字符串中找到正则表达式所匹配的所有子串,并把它们替换为指定的内容,返回一个二元组 (new_string, number_of_subs_made)。语法格式如下:
re.subn(pattern, replacement, string, count=0, flags=0)
regex.subn(replacement, string, count=0)
函数参数
| 参数 | 描述 |
|---|---|
| pattern | 匹配的正则表达式 |
| replacement | 用于替换的文本 |
| string | 要匹配的字符串 |
| count | 至多替换的次数,count=1 至多替换一次。默认为 0,不限制次数。 |
| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。如下表列出正则表达式修饰符——可选标志 |
正则表达式修饰符
| 修饰符 | 描述 |
|---|---|
| re.I | 使匹配对大小写不敏感 |
| re.L | 做本地化识别(locale-aware)匹配 |
| re.M | 多行匹配,影响 ^ 和 $ |
| re.S | 使 . 匹配包括换行在内的所有字符 |
| re.U | 根据 Unicode 字符集解析字符。这个标志影响 \w,\W,\b,\B。 |
| re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
re.subn 使用 pattern 对字符串 string 进行匹配,对匹配项使用 repl 替换。返回一个元组 (new_string, number_of_subs_made)。
可以直观的看到被替换的次数。
一般不用此方法,但是在排错等情况下很好用。
replacement 可以是 string、bytes、function。
subn 方法示例
import re
s = """bottle\nbag\nbig\napple"""
res = re.subn(r'b\w+', 'xyz', s)
print(type(res), res)
# 指定至多替换次数
res = re.subn(r'b\w+', 'xyz', s, count=1)
print(type(res), res)
regex = re.compile(r'b\w+')
res = regex.subn('xyz', s)
print(type(res), res)
regex = re.compile(r'b\w+')
res = regex.subn('xyz', s, count=1)
print(type(res), res)

4.10 分割字符串——spilt
finditer 在字符串中根据指定的正则表达式对字符串进行分割。语法格式如下:
re.split(pattern, string, maxsplit=0, flags=0)
regex.split(self, string, maxsplit=0)
函数参数
| 参数 | 描述 |
|---|---|
| pattern | 匹配的正则表达式 |
| string | 要匹配的字符串 |
| maxsplit | 至多分隔次数,maxsplit=1 至多分隔一次,默认为 0,不限制次数。 |
| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。如下表列出正则表达式修饰符——可选标志 |
正则表达式修饰符
| 修饰符 | 描述 |
|---|---|
| re.I | 使匹配对大小写不敏感 |
| re.L | 做本地化识别(locale-aware)匹配 |
| re.M | 多行匹配,影响 ^ 和 $ |
| re.S | 使 . 匹配包括换行在内的所有字符 |
| re.U | 根据 Unicode 字符集解析字符。这个标志影响 \w,\W,\b,\B。 |
| re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
split 函数用于根据正则表达式分隔字符串,也就是说,将字符串与模式匹配的子字符串都作为分隔符来分隔这个字符串。split 函数返回一个列表形式的分隔结果,每一个列表元素都是分隔的子字符串。
spilt 方法示例
import re
s = """bottle\nbag\nbig\napple"""
# 字符串的 split 方法默认是 \s+ 为分隔符
print(s.split())
# 正则表达式进行分割
print(re.split(r'\s+', s))
s = """
os.path.abspath(path)
path. ([path)]
"""
print(re.split(r'\s', s))
print(re.split(r'[\s.\[\]{}()]', s))
# 可以减少空字符串的出现频率
print(re.split(r'[\s.\[\]{}()]+', s))
res = re.split(r'[\s.\[\]{}()]+', s)
print(f"res:{res}")
new_res = filter(lambda x: x and x.strip(), res)
print(f"type(new_res):{type(new_res)}")
print(f"list(new_res):{list(new_res)}")

4.11 分组★★★
使用小括号的 pattern 捕获的数据被放到了组 group 中。
match、search 函数可以返回 match 对象;findall 返回字符串列表;finditer 返回一个迭代器,迭代器中的每个元素是 match 对象。
如果 pattern 中使用了分组,如果有匹配的结果,会在 match 对象中
- 使用
group(N)方式返回对应分组,1 到 N 是对应的分组,0 返回整个匹配的字符串,N 不写缺省为 0 - 如果使用了命名分组,可以使用
group('name')的方式取分组 - 也可以使用
groups()返回所有组 - 使用
groupdict()返回所有命名的分组
import re
s = """bottle\nbag\nbig\napple"""
res = re.match(r'b\w+', s)
print(res)
print(res.groups())
print('=' * 40)
res = re.match(r'(b)(\w+)', s)
print(res)
print(res.groups())
print('=' * 40)
res = re.search(r'(^a)(\w+)', s, re.M)
print(res)
print(res.groups())
print('=' * 40)
res = re.search(r'(a)(\w+)', s, re.M)
print(res)
# 取所有分组
print(res.groups())
# 取 0 分组,0 分组返回整个匹配的字符串
# 正则的分组号从 1 开始
print(res.group(0))
# 不写分组好,默认为 0print(res.group())
# 取组 1print(res.group(1))
# 取组 2print(res.group(2))
print('=' * 40)
res = re.search(r'(a)(?P<tail>\w+)', s, re.M)
print(res)
# 取所有分组
print(res.groups())
# 取 0 分组,0 分组返回整个匹配的字符串
# 正则的分组号从 1 开始
print(res.group(0))
# 取组 1print(res.group(1))
# 取组 2,命名分组也有组号
print(res.group(2))
# 获取所有的命名分组
print(res.groupdict())
print('=' * 40)

findall 在有分组时的结果
先看下面的示例:
import re
s = """bottle\nbag\nbig\napple"""
print("====findall 中 pattern 有分组====")
res = re.findall(r'(b\w+)\s+(?P<name2>b\w+)\s+(?P<name3>b\w+)', s)
print(type(res))
print(res)
print("====findall 中 pattern 没有分组====")
res = re.findall(r'(?:b\w+)\s+b\w+\s+b\w+', s)
print(type(res))
print(res)
print("====findall 中 pattern 有一个分组====")
res = re.findall(r'(b\w+)\s+b\w+\s+b\w+', s)
print(type(res))
print(res)
print("====findall 中 pattern 有两个分组====")
res = re.findall(r'(b\w+)\s+b\w+\s+b(\w+)', s)
print(type(res))
print(res)

finditer 在有分组时的结果
import re
s = """bottle\nbag\nbig\napple"""
res = re.finditer(r'(b\w+)\s+(?P<name2>b\w+)\s+(?P<name3>b\w+)', s)
print(type(res))
for item in res:
print(type(item), item)
print(item.group())
print(item.groups())
print(item.groupdict())


浙公网安备 33010602011771号