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 分类

  1. BRE

    基本正则表达式,grep、sed、vi 等软件支持。vim 有扩展。

  2. ERE

    扩展正则表达式,egrep(grep-E)、sed-r 等。

  3. 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 匹配非单词的边界
注意
`^` 与 `[^m]` 中的 `^` 的含义并不相同,后者 `^` 表示**除了…**的意思

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|food
industr(?: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)

匹配结果:对 foodwood 进行分组


原始文本:

took food wood foot

正则表达式:对 fw 进行分组

(?:(f|w)ood)

匹配结果:


原始文本:

took food wood foot

正则表达式:对 fw 进行分组

(?:(f|w)(ood))

匹配结果:对 fwood 进行分组


原始文本:

took food wood foot

正则表达式:对 fw 进行分组

((f|w)(ood))

匹配结果:对 foodwoodfwood 进行分组


原始文本:

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 断言

注意
断言就相当于 if 判断语句,满足条件才进行匹配。
> 断言条件不会出现在匹配结果中。

零宽断言

代码 说明 举例
(?=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.I
re.IGNORECASE
Singleline 单行模式,. 可以匹配所有字符,包括换行符 \n re.S
re.DOTALL
Multiline 多行模式,将 \n 看做换行符 re.M
re.MULTILINE
IgnorePatternWhitespace 忽略表达式中的空白字符,如果要使用空白字符,用转义字符,# 可以用来做注释
不推荐使用
re.X
re.VERBOSE
  • 默认模式:

    可以看做待匹配的文本是一行,不能看做多行,. 点号不能匹配换行符,^$ 表示行首和行尾,而行首行尾就是整个字符串的开头和结尾。

  • 单行模式:

    基本和默认模式一样,只是 . 点号终于可以匹配任意一个字符包括换行符,这时所有文本就是一个长长的只有一行的字符串。^ 就是这一行字符串的行首,$ 就是这一行的行尾。

    . 可以匹配所有字符,包括换行符。

    ^ 表示整个字符串的开头,$ 表示整个字符串的结尾。

  • 多行模式:

    重新定义了行的概念,但不影响 . 点号的行为,^$ 还是行首行尾的意思,只不过因为多行模式可以识别换行符了。

    “结束” 指的是 \n 前的字符,注意最后一行结尾可以没有 \n

    . 可以匹配除了换行符之外的字符,多行不影响 . 点号

    ^ 表示行首,$ 行尾,只不过这里的行是每一个行

总结
简单讲,**单行模式**只影响 `.` 点号行为,**多行模式**重新定义行影响了 `^` 和 `$`
注意
注意字符串中看不见的换行符,`\r\n` 会影响 `e$` 的测试,`e$` 只能匹配 `e\n` 。如果要判断 e 是否为结尾,可以使用 `e\r?$`
> > 举例 > > ```python > very very happy > my primary key > ``` > > 上面 2 行 'y' 之后,有可能是 `\r\n` 结尾,此时认为结尾字符是 `\r` 。 > > `y$` 单行匹配 "key" 的 'y' ,多行匹配 "happy" 和 "key" 的 'y' 。 > > `.$` 指的是此行的结尾,而默认模式和单行模式都是一行,指的是这个大字符串的最后一个字符,就是 "key" 的 'y' 。

示例:

原始文本:

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(对象查看对象的方法))
注意
如果想在目标字符串的任意位置查找,需要使用 `search`。

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 对整个字符串,从左至右匹配,返回所有匹配项。匹配成功返回迭代器;匹配失败返回一个空迭代器。

注意
每次选代返回的是 match 对象。

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 可以是 stringbytesfunction

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 可以是 stringbytesfunction

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 中。

matchsearch 函数可以返回 match 对象;findall 返回字符串列表;finditer 返回一个迭代器,迭代器中的每个元素是 match 对象。

如果 pattern 中使用了分组,如果有匹配的结果,会在 match 对象中

  1. 使用 group(N) 方式返回对应分组,1 到 N 是对应的分组,0 返回整个匹配的字符串,N 不写缺省为 0
  2. 如果使用了命名分组,可以使用 group('name') 的方式取分组
  3. 也可以使用 groups() 返回所有组
  4. 使用 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)

总结
- `findall` 在没有分组时,返回一个列表,列表元素是所有匹配到的子串。
> - `findall` 在只有一个分组时,返回一个列表,列表中的每个元素是字符串,内容是每个匹配到的子串中的分组内容。 > - `findall` 在有多个分组时,返回一个列表,列表中的每个元素是元组,元组中的每个元素是每个匹配到的子串中的分组。

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())

posted @ 2026-04-11 02:01  挖掘鱼  阅读(6)  评论(0)    收藏  举报