User:Xyy23330121/Python/正则表达式
正则表达式是处理、分割字符串的重要工具。它可以以一定的模式对字符串进行“匹配”,从而可以按模式筛选字符串或子字符串,并对筛选结果进行操作。
以下语法中,一部分为 Python 的正则表达式专属。在其它的正则表达式实现中使用可能会出错。
正则表达式中,大多数字符都只匹配其自身。比如:正则表达式中 t
会唯一的匹配字符 t
,而正则表达式 e
会唯一的匹配字符 e
等等。
正则表达式满足相加性质。即当“正则表达式A”匹配“字符串a”,而“正则表达式B”匹配“字符串b”时。拼接 A 和 B 得到的“正则表达式A+B”,一定匹配拼接得到的“字符串a+b”。
比如,正则表达式 test
会唯一的匹配字符串 test
。
对于正则表达式而言,上面的“基本匹配”是不足以满足“按模式匹配”的需要的。为了描述模式,正则表达式设置了以下的元字符:
. ^ $ * + ? { } [ ] \ | ( )
这些元字符在正则表达式中表示特殊的意思,从而也就不能匹配字符串中相同字符。元字符之外的字符,都只匹配其自身。
以下将讲解元字符的用法。
匹配位于字符串开头的空字符串。比如:
- 正则表达式
a
和字符串aba
匹配,会得到两个子字符串a
。 - 而正则表达式
^a
和字符串aba
匹配,只会匹配到位于字符串开头的子字符串。
匹配字符串末尾的空字符串,如果字符串末尾是换行符,也可以匹配该换行符前的空字符串。
通过 \
,我们可以在正则表达式转义出匹配元字符的表达式。比如:
\[
唯一地匹配字符[
。\\
唯一地匹配字符\
。
创建正则表达式时,是通过字符串来创建的。如果要创建正则表达式 \\
,需要使用字符串 "\\\\"
或 r"\\"
。为了简便和易于阅读,在创建正则表达式时,请务必使用“原始字符串”的方式。
Python 字符串支持的、以下转义序列也被用作正则表达式的转义。
转义字符 | 含义 |
---|---|
\a |
响铃(BEL) |
\b |
退格(BS) |
\f |
换页(FF) |
\n |
换行(LF) |
\r |
回车(CR) |
\t |
横向制表符(TAB) |
\v |
纵向制表符(VT) |
\ooo |
八进制数ooo 所表示的字符
|
\xhh |
二位十六进制数hh 所表示的字符
|
\N{name} |
Unicode 数据库中,名为name 的字符,name 可以为别名。有些别名不被支持。
|
\uxxxx |
四位十六进制数xxxx 所表示的字符
|
\Uxxxxxxxx |
八位十六进制数xxxxxxxx 表示的字符
|
特别的,以下几种方式与字符串中的使用有区别:
- 对于
\ooo
,只有以下两种方式会被视为此转义:- 首位数字是
0
,比如\01
- 使用了完整的三位数字,比如
\123
- 首位数字是
- 对于
\b
,只有在“字符类”中,才认为是退格符。 - 对于
\uxxxx
、\Uxxxxxxxx
和\N{name}
,仅在 Unicode 字符串模式才会被识别。
转义 | 解释 |
---|---|
\A | 只匹配字符串开头 |
\b | 只匹配单词开头或结尾 单词开头和结尾被定义为:
|
\B | 非 \b 的空字符串 |
\Z | 只匹配字符串结尾 |
关于 \w
和 \W
,参见下面 ASCII 模式和 Unicode 模式 章节。
被 []
括住的部分为字符类。字符类会匹配任何在类中的字符,比如:[aA1]
能匹配三个字符串:a
、A
和 1
。
除\
以外的元字符在字符类中都不起作用。比如:[$]
会唯一的匹配字符 $
。
如果要添加 ]
到字符类中,则必须要使用转义方法 \]
。
除上述规则以外,为简便起见,字符类有以下的特殊的表示方式:
用 -
可以在字符类中表示“范围”,比如:
[a-c]
能匹配a
、b
、c
。[a-zA-Z0-9]
能匹配任何单个英文字母字符。
需要注意的是,如果把 -
放在首尾,则不会表示“范围”,而是表示单独的 -
字符。比如:
[-a]
能匹配-
、a
。[-a-c]
能匹配-
、a
、b
、c
。
若字符类中第一个字符为 ^
,则认为是对字符类取反,比如:
[^a]
能匹配任何不是a
的字符。[^a-z]
能匹配任何不是小写英文字母的字符。
需要注意的是,如果把 ^
放在中间,则没有此效果,比如:
[ ^]
能匹配空格字符和^
用 .
可以匹配除了换行符以外的任意字符。
通过以下的方式,可以设定匹配次数。
正则表达式 ab*
会匹配字符串 a
、ab
、abb
等。
正则表达式 ab+
会匹配字符串 ab
、abb
等。但不会匹配 a
。
正则表达式 ab?
会匹配字符串 a
和字符串 ab
。
比如:正则表达式 a{3}
会匹配字符串 aaa
。
使用 a{m}
时,m 必须是正整数。
m 和 n 必须是正整数。
比如:正则表达式 a{2,3}
会匹配字符串 aa
和字符串 aaa
。
特别的,使用 {,n}
会匹配 0 ~ n 次。使用 {m,}
会匹配 m ~ 任意次。使用 {,}
等同于使用 *
。
在进行重复匹配时,默认会先尽可能多地进行匹配。在正则表达式 ^[a-z]*re
和 abrer
进行匹配时,会进行以下步骤:
[a-z]*
匹配abrer
r
到字符串末尾,无法匹配,回退
[a-z]*
匹配abre
r
匹配r
e
到字符串末尾,无法匹配,回退
[a-z]*
匹配abr
r
无法匹配e
,回退
[a-z]*
匹配ab
r
匹配r
e
匹配e
- 正则表达式结束,匹配完毕,匹配到子字符串
abre
。
可以看出 [a-z]*
会先尝试匹配 5 个字符、再尝试匹配 4 个字符,从匹配字符数从多到少进行尝试。这种尝试的模式被称为“贪婪模式”。
在代表重复匹配的结构后添加 ?
,则会改为匹配字符数从少到多进行尝试。
比如,在正则表达式 ^[a-z]*?re
和 abrer
进行匹配时,会进行以下步骤:
[a-z]*?
匹配a
r
无法匹配b
,回退
[a-z]*?
匹配ab
r
匹配r
e
匹配e
- 正则表达式结束,匹配完毕,匹配到子字符串
abre
。
在代表重复匹配的结构后添加 +
,则会在贪婪模式的基础上,不允许回退。这也被称为“占有模式”。
比如,在正则表达式 ^a?[a-z]*+re
和 abrer
进行匹配时,会进行以下步骤:
a?
匹配a
[a-z]*+
匹配brer
并“占有”r
无法匹配字符串末尾,回退
不能回退并修改[a-z]*+
的匹配结果
回退并修改在[a-z]*+
之前的a?
a?
匹配空字符串[a-z]*+
匹配abrer
并“占有”r
无法匹配字符串末尾,回退
不能回退并修改[a-z]*+
的匹配结果
- 正则表达式结束,匹配完毕,没有匹配到字符串。
“占有模式”和“非贪婪模式”不能同时设置。
正则表达式 a|b
会匹配字符串 a
和 b
。
特别的,如果左边的“分支”匹配成功了,就不会尝试对右边的分支进行匹配。比如:
[a-z]*|[0-9]*114
和字符串进行匹配时,会先匹配 [a-z]*114
,匹配失败后再尝试匹配 [0-9]*114
。如果 [a-z]*114
匹配成功,就不会尝试 [0-9]*114
了。举个例子:
[a-z]*|[0-9]*114
和1140114
匹配:- 尝试匹配
[a-z]*114
[a-z]*
匹配到空字符串114
匹配到114
匹配成功,匹配到114
- 尝试匹配
在匹配过程中,根本不会尝试匹配 [0-9]*114
。即便 [0-9]*114
能匹配完整的 1140114
。
我们可以把一系列正则表达式作成组合,并对组合进行操作。比如:
- 正则表达式
(ab)?
会匹配字符串ab
和字符串abab
- 正则表达式
(ab)|c
会匹配字符串ab
和字符串c
字符 | 匹配方式 |
---|---|
i |
忽略大小写 |
m |
允许多行匹配 |
s |
. 会匹配任意字符(包括换行符) |
x |
带注释的正则表达式 |
a |
ASCII 模式 |
u |
Unicode 模式 |
L |
按本地语言区域模式 |
以上的匹配方式并不绝对。如果在正则表达式中,出现了形如 (?a)
的组,则会按照该组的内容设置匹配方式。
这种类型的组不会匹配任何内容,它只匹配空字符串。
右表列出了该组中,可用的字符。这些字符可以进行组合,以同时设置多个匹配方式。
其中,ASCII 模式、 Unicode 模式和“按本地语言区域”模式互相矛盾,因此不能相互组合。在未设置这些模式时,默认使用 Unicode 模式。“按本地语言区域”的模式不可靠,不建议使用。
ASCII 模式和 Unicode 模式设置以下转义得到的字符类的行为:
转义 | ASCII 模式 | Unicode 模式 |
---|---|---|
\d | [0-9] | 任何属于 Unicode 字符类别 ND 的字符。 |
\D | [^0-9] | 任何非 \d 的字符。
|
\s | [ \t\n\r\f\v] | 任何 Unicode 空白字符 |
\S | [^ \t\n\r\f\v] | 任何非 Unicode 空白字符 |
\w | [a-zA-Z0-9_] | 任何 str.isalnum() 方法返回 True 的字符 以及下划线 |
\W | [^a-zA-Z0-9_] | 任何 str.isalnum() 方法返回 False 的字符 不包含下环线 |
多行匹配模式会影响 ^
和 $
的行为。
在设置了多行匹配模式后:
^
会匹配位于换行符后的空字符串。$
会匹配位于换行符前的空字符串。
此时,允许创建带有注释的、美观的正则表达式。以下两种方式得到的结果是相同的:
pattern = r"(0b[01]*)|(0o[0-7]*)|([0-9]*)|(0x[0-9a-fA-F]*)"
r = re.compile(pattern) #编译正则表达式
pattern = r"""(?x) #匹配数字:
(0b[01]*) #二进制数字
| (0o[0-7]*) #八进制数字
| ([0-9]*) #十进制数字
| (0x[0-9a-fA-F]*) #十六进制数字
"""
r = re.compile(pattern) #编译正则表达式
此时,正则表达式会忽略字符串中的空格,以及多行字符串中、自动加入的换行符。
如果要匹配空格,则使用 \
。如果要匹配换行符,则使用 \n
。
仅对其中的正则表达式启用或弃用某匹配方式。比如:
(?i)ab
会匹配:ab
、Ab
、aB
和AB
。(?i)a(?-i:b)
会匹配:ab
、Ab
。弃用了对b
的“忽略大小写模式”。a(?i:b)
会匹配:aB
、AB
。仅对b
启用“忽略大小写模式”。
特别的,不允许简单地弃用“ASCII 模式”“Unicode 模式”和“按本地语言区域模式”。
该组会对组中的表达式启用“占有模式”,比如:
x*+
等同于(?>x*)
。
此方法会影响 Python 在进行匹配时、保存的结果。比如:
(xyz)
在匹配时,会在结果中保存匹配到的xyz
。(?:xy(z))
在匹配时,会匹配到相同的内容,但结果中仅保存z
。
通过这种方式,可以给组进行命名。re.Match 实例的多个方法,以及 re.Pattern 实例的 sub 方法会引用命名组匹配到的内容。
被命名的组可以用以下方式,在正则表达式中引用。
仅匹配第一次匹配到 (?P<name>...)
时、匹配到的内容。
import re
p = re.compile(r'''(?x)
<(?P<ele_name>[a-zA-Z]+).*?> #匹配<html>
.* #匹配中间
</(?P=ele_name)> #匹配</html>
''')
s = '<p>Paragraph</pi>'
s1 = '<p>Paragraph</p>'
print(p.match(s).string[matchobj.start():matchobj.end()]) #输出空字符串,没有得到匹配。
print(p.match(s1).string[matchobj.start():matchobj.end()]) #输出:<p>Paragraph</p>
类似 (?P=name)
,但此方式也可以用于引用一般的组。
仅匹配第一次匹配到第 num 个组时,匹配到的内容。
作为注释使用,仅匹配空字符串。
仅匹配空字符串。
其中的正则表达式和当前位置右侧的内容相匹配,则匹配空字符串成功。否则匹配失败。
比如:正则表达式 Isaac(?= Newton)
将会匹配 Isaac Newton
中的 Isaac
,但不会匹配 Isaac Brown
中的 Isaac
。
这种方式被称为前视断言 (lookahead assertion)。
仅匹配空字符串。
其中的正则表达式和当前位置右侧的内容不匹配,则匹配空字符串成功。否则匹配失败。
比如:正则表达式 Isaac(?! Newton)
将会匹配 Isaac Brown
中的 Isaac
,但不会匹配 Isaac Newton
中的 Isaac
。
这种方式被称为否定型前视断言 (negative lookahead assertion)。
仅匹配空字符串。
其中的正则表达式与当前位置左侧的内容相匹配,则匹配空字符串成功。否则匹配失败。
这种方式被称为肯定型后视断言 (positive lookbehind assertion)。
仅匹配空字符串。
其中的正则表达式和当前位置左侧的内容不匹配,则匹配空字符串成功。否则匹配失败。
这种方式被称为否定型后视断言 (negative lookbehind assertion)。
在匹配时,之前已有的、\num
或 (?P=name)
所示的组、匹配的并非空字符串,则按照 yes-pattern 进行匹配。否则,按照 no-pattern 进行匹配。
旗标 | 匹配方式 |
---|---|
re.I re.IGNORECASE |
忽略大小写 |
re.M re.MULTILINE |
允许多行匹配 |
re.S re.DOTALL |
. 会匹配任意字符(包括换行符) |
re.X re.VERBOSE |
带注释的正则表达式 |
re.A re.ASCII |
ASCII 模式 |
re.U re.UNICODE |
Unicode 模式 |
re.L re.LOCALE |
按本地语言区域模式 |
re.NOFLAG |
表示空旗标。 特别的:re.NOFLAG 就是整数 0 。
|
re.DEBUG |
显示有关编译表达式的调试信息。 |
旗标和上述 设置匹配方式 章节的效果是相同的。右表列出了可用的旗标。
在使用旗标时,可以用“按位或”运算符 |
组合多个旗标,并把结果传入下面函数中的 flags 参数中。
特别的:re.NOFLAG
和任何其它旗标进行组合,都会返回其它旗标。
re.DEBUG
会在使用正则表达式时,返回编译的结果。
编译正则表达式。返回一个 re.Pattern
实例。re 模块是通过该类型的实例方法来进行匹配操作的。
属性 | 解释 |
---|---|
flags | 旗标。是 compile 函数获取的旗标以及正则表达式中用 (?imsxauL) 设置的匹配方式的组合。
|
groups | 捕获到的模式串中组的数量。 |
groupindex | 映射由 (?P<name>) 定义的、有命名的组,其命名:索引位置 组成的的字典。如果没有有命名的组,那字典就是空的。 |
pattern | 编译对象的原始样式字符串。 |
在 string[pos: endpos]
中,查找匹配正则表达式的第一个位置,并返回对应的 re.Match
实例。如果没有匹配的位置,则返回 None。
类似 search
方法,但是从 string[pos: endpos]
的开头开始匹配:不在 string[pos: endpos]
最开头的、匹配的位置都不会被匹配到。
如果 string[pos: endpos]
整个字符串都与正则表达式匹配,则返回相应的 re.Match
实例。
在 string[pos: endpos]
中,查找匹配正则表达式的、所有非重复的位置。
每个位置,都会生成一个 re.Match
实例。该函数会返回生成的 re.Match
实例组成的迭代器。
类似 finditer
方法。但是输出不同。
如果正则表达式中不包含一般的组,则会输出匹配的子字符串组成的列表。而如果正则表达式中包含组,则只输出组中的内容。比如:
import re
p = re.compile(r"\b[tT]\w+\b")
s1 = "This is a test string."
print(p.findall(s1)) #输出:['This', 'test']
q = re.compile(r"\b(\w+)=([^ ]+)")
s2 = '<img src="./image.png" width=100 height=100 />'
print(q.findall(s2)) #输出:[('src', '"./image.png"'), ('width', '100'), ('height', '100')]
以 string
中所有匹配的子字符串为分割符,返回分割后的、子字符串的列表。
最多进行 maxsplit
次分割。
特别的:
- 如果 string 的开头被匹配,则返回的列表会以空字符串开头。
- 如果正则表达式中包含一般的组(而非
(?:...)
的组),则会在分割中保留组内匹配的内容。
import re
p = re.compile("(a)(b)")
q = re.compile("(?:a)(?:b)")
s = "ab"
print(p.split(s)) #输出:['c', 'a', 'b', 'c']
print(q.split(s)) #输出:['c', 'c']
可以看出,使用一般的组的 p
在进行分割时,输出的结果中包含了组中的内容。而使用非捕获模式的 q
则没有进行这样的操作。
按 repl 所示的模式替换 string 中、匹配的内容。最多替换 count 次。repl 可以是字符串或函数。
如果 repl 是字符串,则直接进行替换,比如:
import re
p = re.compile(r"\b(\w+)=([^ ]+)")
s = '<img src="./image.png" width=100 height=100 />'
r = 'REPLACE'
print(p.sub(r, s)) #输出:<img REPLACE REPLACE REPLACE />
特别的, repl 中包含的、字符串转义序列的原始序列都会被转义。并且一些特殊的转义也会被执行:
转义 | 注释 |
---|---|
\g<num> |
正则表达式中,第 num 个组匹配到的内容 |
\num |
正则表达式中,第 num 个组匹配到的内容 由于和 \ooo 冲突,num 不能超过 100,且开头不能为 0。 |
\g<name> |
正则表达式中,名为 name 的组匹配到的内容 |
import re
p = re.compile(r"\b(?P<arg>\w+)=([^ ]+)")
s = '<img src="./image.png" />'
r = r'\g<1>\1\g<arg>'
print(p.sub(r, s)) #输出:<img srcsrcsrc />
如果 repl 是函数,则该函数应可以传入每次匹配时,对应的 re.Match
实例,并返回替换的结果。
import re
p = re.compile(r"\b(\w+)=([^ ]+)")
s = '<img src="./image.png" width=100 height=100 />'
def r(matchobj):
print(matchobj.string[matchobj.start():matchobj.end()])
return "REPLACE"
print(p.sub(r, s))
输出为:
src="./image.png"
width=100
height=100
<img REPLACE REPLACE REPLACE />
类似 sub
方法,但返回一个元组: (替换后字符串, 替换次数)
。
如果只匹配一次,也可以直接使用 re 模块的以下函数:
函数 | 等同于 |
---|---|
re.search(pattern, string, flags=0) | re.compile(pattern, flags).search(string) |
re.match(pattern, string, flags=0) | re.compile(pattern, flags).match(string) |
re.fullmatch(pattern, string, flags=0) | re.compile(pattern, flags).fullmatch(string) |
re.split(pattern, string, maxsplit=0, flags=0) | re.compile(pattern, flags).split(string, maxsplit) |
re.findall(pattern, string, flags=0) | re.compile(pattern, flags).findall(string) |
re.finditer(pattern, string, flags=0) | re.compile(pattern, flags).finditer(string) |
re.sub(pattern, repl, string, count=0, flags=0) | re.compile(pattern, flags).sub(repl, string, count) |
re.subn(pattern, repl, string, count=0, flags=0) | re.compile(pattern, flags).subn(repl, string, count) |
替换字符串 template 的内容,使其被转义。并返回替换后的字符串。
除字符串的转义之外,\1
、\g<1>
等也会被转义。
返回指定的组所匹配的内容。
其参数可以为正整数,代表组的索引(以 1 开头)。也可以是字符串,代表组的名称。
如果只有一个参数,则会返回字符串。如果有多个参数,则会返回字符串组成的元组。
特别的:
- 参数 0 对应的是匹配的完整子字符串。
- 不输入任何参数时,等同于
self.group(0)
import re
p = re.compile(r"\b(?P<attr>\w+)=(?P<value>[^ ]+)")
s = '<img src="./image.png" width=100 height=100 />'
m = p.search(s)
print(m.group()) #输出:src="./image.png"
print(m.group(0)) #输出:src="./image.png"
print(m.group(1)) #输出:src
print(m.group(1,2)) #输出:('src', '"./image.png"')
print(m.group("attr","value")) #输出:('src', '"./image.png"')
使用 self[g]
来调用此方法。
等同于 self.group(g)
。
返回包含所有匹配子组的字符串。对于总共有 n 个组的情况,等同于 self.group(1,2,...,n)
。
形如 (pattern)*
的组可能一次都不会被匹配。此时,该组对应的位置会输出 default 的内容。
类似 groups 方法,但是返回的是 组的名称:匹配到的内容
的字典。
该属性保存最后一个匹配的整数索引值。如果没有匹配的组,该属性为 None。
嵌套中的组不会被计数。比如,对于字符串 'ab',表达式 (a)b, ((a)(b)), 和 ((ab)) 将得到 lastindex == 1 , 而 (a)(b) 会得到 lastindex == 2 。
最后一个匹配的命名组名字。如果没有匹配的组,该属性为 None。
返回 group 所示的组,在原始字符串中起始位置的索引。
如果不输入 group 或 group 为 0,则返回匹配开始的位置。
返回 group 所示的组,在原始字符串中结束位置的索引。
如果不输入 group 或 group 为 0,则返回匹配结束的位置。
返回 (start([group]), end([group])) 元组。
生成 re.Match 需要用到 使用 re.compile(pattern).match(string, pos, endpos) 或 re.compile(pattern).search(string, pos, endpos) 等方法。以下属性表示其中的参数或对象。
生成此 re.Match 实例所使用的参数 pos。
生成此 re.Match 实例所使用的参数 endpos。
生成此 re.Match 实例所使用的 re.Pattern 实例。
生成此 re.Match 实例所使用的参数 string。即被匹配的字符串。
生成一个能匹配 pattern 字符串的、正则表达式字符串。
清除正则表达式的缓存。
由于“多次匹配”的匹配机制,一些正则表达式可能会导致匹配时间随字符串长度指数增长,比如以下是导致 Cloudflare 在 2019 年 7 月发生全球宕机事故的正则表达式的一部分:
.*(?:.*=.*)
如果用这个正则表达式来匹配形如 title=User:Xyy23330121
的内容,它会:
.*
匹配到title=User:Xyy23330121
.*
匹配到空字符串=
到字符串尾,无法匹配,回退
- 空字符串无法回退,继续向前回退。
.*
匹配到title=User:Xyy2333012
.*
匹配到1
=
到字符串尾,无法匹配,回退
.*
匹配到空字符串=
匹配到1
,无法匹配,回退
- 空字符串无法回退,继续向前回退。
.*
匹配到title=User:Xyy233301
- ...
从结论而言,它会尝试匹配总共 1 + 2 + ... + 16 = 136 次才会正确匹配所有内容。而如果等号后面的内容更多,情况只会更糟。
请在设置正则表达式时尽可能慎重。