为了在 Python 中进行正则表达式处理,我们使用标准库中的 re 模块。它允许你使用正则表达式模式提取、替换和分割字符串。
- re — Regular expression operations — Python 3.10.0 Documentation
- Regular Expression HOWTO — Python 3.10.0 Documentation
在本节中,我们将首先解释re模块的功能和方法。
- 编译正则表达式模式。
compile()
- 匹配对象
- 检查字符串的开头是否匹配,提取。
match()
- 检查是否有不限于开头的比赛。
search()
- 检查整个字符串是否匹配。
fullmatch()
- 获得所有匹配部件的清单。
findall()
- 作为一个迭代器获得所有匹配的部分。
finditer()
- 更换匹配的部件。
sub()
,subn()
- 用正则表达式模式拆分字符串。
split()
之后,我将解释re模块中可以使用的正则表达式的元字符(特殊字符)和特殊序列。基本上,它是标准的正则表达式语法,但要注意设置标志(尤其是re.ASCII)。
- Python中的正则表达式元字符、特殊序列和注意事项
- 设置旗帜
- 限于ASCII字符。
re.ASCII
- 不区分大小写。
re.IGNORECASE
- 匹配每一行的开头和结尾。
re.MULTILINE
- 指定多个标志
- 限于ASCII字符。
- 贪婪和非贪婪的匹配
编译正则表达式模式:compile()
在re模块中,有两种方法来执行正则表达式处理。
用功能运行
首先是一个函数。re.match()
,re.sub()
像这样的函数可用于执行提取、替换和其他使用正则表达式模式的过程。
这些函数的细节将在后面介绍,但在所有的函数中,第一个参数是正则表达式模式的字符串,然后是要处理的字符串,以此类推。例如,在执行替换的re.sub()中,第二个参数是替换的字符串,第三个参数是要处理的字符串。
import re
s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'
m = re.match(r'([a-z]+)@([a-z]+)\.com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
result = re.sub(r'([a-z]+)@([a-z]+)\.com', 'new-address', s)
print(result)
# new-address, new-address, ccc@zzz.net
请注意,本例中正则表达式模式中的[a-z]意味着从a到z的任何字符(即小写字母),而+意味着重复前面的模式(本例中为[a-z])一次或多次。[a-z]+ 匹配任何重复一个或多个小写字母字符的字符串。
.是一个元字符(具有特殊含义的字符),必须用反斜杠转义。
由于正则表达式模式字符串经常使用大量的反斜线,所以使用原始字符串是很方便的,就像例子中那样。
在正则表达式模式对象的一个方法中运行
在re模块中处理正则表达式的第二个方法是正则表达式模式对象方法。
使用re.compile(),你可以编译一个正则表达式模式字符串来创建一个正则表达式模式对象。
p = re.compile(r'([a-z]+)@([a-z]+)\.com')
print(p)
# re.compile('([a-z]+)@([a-z]+)\\.com')
print(type(p))
# <class 're.Pattern'>
re.match()
,re.sub()
例如,与这些函数相同的过程可以作为正则表达式对象的方法match(),sub()执行。
m = p.match(s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
result = p.sub('new-address', s)
print(result)
# new-address, new-address, ccc@zzz.net
下面描述的所有re.xxx()函数也都作为正则表达式对象的方法提供。
如果你正在重复一个使用相同模式的过程,用re.compile()生成一个正则表达式对象,并在周围使用它,会更有效率。
在下面的示例代码中,为了方便起见,没有编译就使用了该函数,但如果你想重复使用同一个模式,建议提前编译,并作为正则表达式对象的一个方法执行。
匹配对象
match(), search()等返回一个匹配对象。
s = 'aaa@xxx.com'
m = re.match(r'[a-z]+@[a-z]+\.[a-z]+', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
print(type(m))
# <class 're.Match'>
匹配的字符串和位置是通过匹配对象的以下方法获得的。
- 获得比赛的位置。
start()
,end()
,span()
- 获取匹配的字符串。
group()
- 获取每组的字符串。
groups()
print(m.start())
# 0
print(m.end())
# 11
print(m.span())
# (0, 11)
print(m.group())
# aaa@xxx.com
如果你用小括号()将正则表达式模式的一部分括在一个字符串中,那么该部分将被作为一个组来处理。在这种情况下,在group()中匹配每个组的部分的字符串可以作为一个元组得到。
m = re.match(r'([a-z]+)@([a-z]+)\.([a-z]+)', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
print(m.groups())
# ('aaa', 'xxx', 'com')
检查一个字符串的开头是否匹配,提取: match()
match()如果字符串的开头与模式匹配,则返回一个匹配对象。
如上所述,匹配对象可以用来提取匹配的子串,或者简单地检查是否进行了匹配。
match()将只检查开头。如果在开头没有匹配的字符串,它将返回None。
s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'
m = re.match(r'[a-z]+@[a-z]+\.com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
m = re.match(r'[a-z]+@[a-z]+\.net', s)
print(m)
# None
检查不限于开头的匹配,提取:search()
和match()一样,如果匹配,它返回一个匹配对象。
如果有多个匹配部分,将只返回第一个匹配部分。
s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'
m = re.search(r'[a-z]+@[a-z]+\.net', s)
print(m)
# <re.Match object; span=(26, 37), match='ccc@zzz.net'>
m = re.search(r'[a-z]+@[a-z]+\.com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
如果你想获得所有匹配的部分,使用findall()或finditer(),如下所述。
检查整个字符串是否匹配:fullmatch()
要检查整个字符串是否与正则表达式模式匹配,请使用fullmatch()。这很有用,例如,检查一个字符串是否可以作为电子邮件地址。
如果整个字符串匹配,将返回一个匹配对象。
s = 'aaa@xxx.com'
m = re.fullmatch(r'[a-z]+@[a-z]+\.com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
如果有不匹配的部分(只有部分匹配或根本没有匹配),则返回无。
s = '!!!aaa@xxx.com!!!'
m = re.fullmatch(r'[a-z]+@[a-z]+\.com', s)
print(m)
# None
fullmatch() 是在 Python 3.4 中添加的。如果你想在早期版本中做同样的事情,请使用 match() 并在末尾使用匹配的元字符 $。如果整个字符串从头到尾都不匹配,它将返回 None。
s = '!!!aaa@xxx.com!!!'
m = re.match(r'[a-z]+@[a-z]+\.com$', s)
print(m)
# None
获取所有匹配部件的列表:findall()
findall()返回一个所有匹配子字符串的列表。请注意,该列表中的元素不是匹配对象,而是字符串。
s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'
result = re.findall(r'[a-z]+@[a-z]+\.[a-z]+', s)
print(result)
# ['aaa@xxx.com', 'bbb@yyy.com', 'ccc@zzz.net']
可以使用内置函数len()检查匹配部分的数量,该函数返回列表中元素的数量。
print(len(result))
# 3
在正则表达式模式中用小括号()分组,会返回一个图元的列表,其元素是每个组的字符串。这等同于匹配对象中的 groups()。
result = re.findall(r'([a-z]+)@([a-z]+)\.([a-z]+)', s)
print(result)
# [('aaa', 'xxx', 'com'), ('bbb', 'yyy', 'com'), ('ccc', 'zzz', 'net')]
组中的小括号()可以嵌套,所以如果你想同时获得整个匹配,只需将整个匹配括在小括号()中。
result = re.findall(r'(([a-z]+)@([a-z]+)\.([a-z]+))', s)
print(result)
# [('aaa@xxx.com', 'aaa', 'xxx', 'com'), ('bbb@yyy.com', 'bbb', 'yyy', 'com'), ('ccc@zzz.net', 'ccc', 'zzz', 'net')]
如果没有找到匹配,将返回一个空元组。
result = re.findall('[0-9]+', s)
print(result)
# []
作为一个迭代器获取所有匹配的部分:finditer()
finditer()以迭代器的形式返回所有匹配的部分。这些元素不是像findall()那样的字符串,而是匹配对象,所以你可以得到匹配部分的位置(索引)。
迭代器本身不能用print()打印出来以获得其内容。如果你使用内置函数next()或for语句,你可以一个一个地获得内容。
s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'
result = re.finditer(r'[a-z]+@[a-z]+\.[a-z]+', s)
print(result)
# <callable_iterator object at 0x10b0efa90>
print(type(result))
# <class 'callable_iterator'>
for m in result:
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
# <re.Match object; span=(13, 24), match='bbb@yyy.com'>
# <re.Match object; span=(26, 37), match='ccc@zzz.net'>
它也可以用list()转换为一个列表。
l = list(re.finditer(r'[a-z]+@[a-z]+\.[a-z]+', s))
print(l)
# [<re.Match object; span=(0, 11), match='aaa@xxx.com'>, <re.Match object; span=(13, 24), match='bbb@yyy.com'>, <re.Match object; span=(26, 37), match='ccc@zzz.net'>]
print(l[0])
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
print(type(l[0]))
# <class 're.Match'>
print(l[0].span())
# (0, 11)
如果你想获得所有匹配部分的位置,列表理解的符号比list()更方便。
print([m.span() for m in re.finditer(r'[a-z]+@[a-z]+\.[a-z]+', s)])
# [(0, 11), (13, 24), (26, 37)]
迭代器按顺序取出元素。注意,如果你在到达终点后试图提取更多的元素,你将会一无所获。
result = re.finditer(r'[a-z]+@[a-z]+\.[a-z]+', s)
for m in result:
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
# <re.Match object; span=(13, 24), match='bbb@yyy.com'>
# <re.Match object; span=(26, 37), match='ccc@zzz.net'>
print(list(result))
# []
替换匹配部分:sub(), subn()
使用sub(),你可以用另一个字符串替换匹配的部分。被替换的字符串将被返回。
s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'
result = re.sub(r'[a-z]+@[a-z]+\.com', 'new-address', s)
print(result)
# new-address, new-address, ccc@zzz.net
print(type(result))
# <class 'str'>
当用小括号()分组时,匹配的字符串可以用在替换的字符串中。
默认情况下,支持以下内容。请注意,对于非原始字符串的普通字符串,必须在反斜杠之前列出一个反斜杠,以转义反斜杠。
\1 | 第一个小括号 |
\2 | 第二个括号 |
\3 | 第三个括号 |
result = re.sub(r'([a-z]+)@([a-z]+)\.com', r'\1@\2.net', s)
print(result)
# aaa@xxx.net, bbb@yyy.net, ccc@zzz.net
?P<xxx>
如果你在正则表达式模式的圆括号开头写上这个名字,你可以用这个名字而不是数字来指定这个组,如下图所示。\g<xxx>
result = re.sub(r'(?P<local>[a-z]+)@(?P<SLD>[a-z]+)\.com', r'\g<local>@\g<SLD>.net', s)
print(result)
# aaa@xxx.net, bbb@yyy.net, ccc@zzz.net
参数count指定替换的最大数量。只有左边的计数会被替换。
result = re.sub(r'[a-z]+@[a-z]+\.com', 'new-address', s, count=1)
print(result)
# new-address, bbb@yyy.com, ccc@zzz.net
subn()返回一个元组,包括被替换的字符串(与sub()的返回值相同)和被替换部分的数量(符合模式的数量)。
result = re.subn(r'[a-z]+@[a-z]+\.com', 'new-address', s)
print(result)
# ('new-address, new-address, ccc@zzz.net', 2)
指定参数的方法与sub()相同。你可以使用由圆括号分组的部分,或者指定参数数。
result = re.subn(r'(?P<local>[a-z]+)@(?P<SLD>[a-z]+)\.com', r'\g<local>@\g<SLD>.net', s)
print(result)
# ('aaa@xxx.net, bbb@yyy.net, ccc@zzz.net', 2)
result = re.subn(r'[a-z]+@[a-z]+\.com', 'new-address', s, count=1)
print(result)
# ('new-address, bbb@yyy.com, ccc@zzz.net', 1)
用正则表达式模式分割字符串: split()
split()将字符串中符合模式的部分拆开,并以列表形式返回。
请注意,第一个和最后一个匹配项将在结果列表的开头和结尾包含空字符串。
s = '111aaa222bbb333'
result = re.split('[a-z]+', s)
print(result)
# ['111', '222', '333']
result = re.split('[0-9]+', s)
print(result)
# ['', 'aaa', 'bbb', '']
maxsplit参数指定了最大的分割数(件)。只有来自左边的计数会被分割。
result = re.split('[a-z]+', s, 1)
print(result)
# ['111', '222bbb333']
Python中的正则表达式元字符、特殊序列和注意事项
在Python 3 re模块中可以使用的主要正则表达式元字符(特殊字符)和特殊序列如下
元字符 | 内容 |
---|---|
. | 除换行之外的任何单个字符(包括带有DOTALL标志的换行)。 |
^ | 字符串的开头(也与带有MULTILINE标志的每一行的开头相匹配)。 |
$ | 字符串的结尾(也与带有MULTILINE标志的每一行的结尾相匹配)。 |
* | 重复前一个模式0次以上 |
+ | 至少重复一次之前的模式。 |
? | 重复上一个模式0或1次 |
{m} | 重复前一个模式m次 |
{m, n} | 最后一种模式。m ~n 重复 |
[] | 一组字符[] 匹配这些字符中的任何一个 |
| | 或A|B 与A或B模式相匹配 |
特殊序列 | 内容 |
---|---|
\d | Unicode十进制数字(通过ASCII标志限制为ASCII数字)。 |
\D | \d 意思是与此相反。 |
\s | Unicode空白字符(通过ASCII标志限制为ASCII空白字符)。 |
\S | \s 意思是与此相反。 |
\w | Unicode 字符和下划线(按ASCII标志限制为ASCII字母数字字符和下划线)。 |
\W | \w 意思是与此相反。 |
并非所有的都在本表中列出。完整的清单见官方文件。
还要注意的是,在Python 2中有些含义是不同的。
设置旗帜
如上表所示,一些元字符和特殊序列根据标志的不同而改变其模式。
这里只涉及主要的标志。其余内容请参见官方文档。
限于ASCII字符:re.ASCII
\w
对于 Python 3 字符串,这也将默认匹配双字节汉字、字母数字字符等。它不等同于下面的内容,因为它不是一个标准的正则表达式。[a-zA-Z0-9_]
m = re.match(r'\w+', '漢字ABC123')
print(m)
# <re.Match object; span=(0, 11), match='漢字ABC123'>
m = re.match('[a-zA-Z0-9_]+', '漢字ABC123')
print(m)
# None
如果你在每个函数的参数标志中指定re.ASCII,或者在正则表达式模式字符串的开头添加以下内联标志,它将只匹配ASCII字符(它不会匹配双字节的日文、字母数字字符等)。(?a)
在这种情况下,以下两个是等同的。\w
=[a-zA-Z0-9_]
m = re.match(r'\w+', '漢字ABC123', flags=re.ASCII)
print(m)
# None
m = re.match(r'(?a)\w+', '漢字ABC123')
print(m)
# None
这同样适用于用re.compile()进行编译时。使用参数flags或inline flags。
p = re.compile(r'\w+', flags=re.ASCII)
print(p)
# re.compile('\\w+', re.ASCII)
print(p.match('漢字ABC123'))
# None
p = re.compile(r'(?a)\w+')
print(p)
# re.compile('(?a)\\w+', re.ASCII)
print(p.match('漢字ABC123'))
# None
ASCII也可作为简称re。A. 你可以使用其中之一。
print(re.ASCII is re.A)
# True
\W,与\W相反,也受到re.ASCII和内联标志的影响。
m = re.match(r'\W+', '漢字ABC123')
print(m)
# None
m = re.match(r'\W+', '漢字ABC123', flags=re.ASCII)
print(m)
# <re.Match object; span=(0, 11), match='漢字ABC123'>
与 \w 一样,下面两个默认情况下同时匹配单字节和双字节字符,但如果指定了 re.ASCII 或 inline 标志,则仅限于单字节字符。
- 匹配数字
\d
- 匹配一个空白处
\s
- 匹配非数字
\D
- 匹配任何非空格。
\S
m = re.match(r'\d+', '123')
print(m)
# <re.Match object; span=(0, 3), match='123'>
m = re.match(r'\d+', '123')
print(m)
# <re.Match object; span=(0, 3), match='123'>
m = re.match(r'\d+', '123', flags=re.ASCII)
print(m)
# <re.Match object; span=(0, 3), match='123'>
m = re.match(r'\d+', '123', flags=re.ASCII)
print(m)
# None
m = re.match(r'\s+', ' ') # full-width space
print(m)
# <re.Match object; span=(0, 1), match='\u3000'>
m = re.match(r'\s+', ' ', flags=re.ASCII)
print(m)
# None
不区分大小写。re.IGNORECASE
默认情况下,它是区分大小写的。要想同时匹配,你需要在模式中同时包含大写和小写字母。
re.IGNORECASE
如果指定了这个,它将不分大小写进行匹配。相当于标准正则表达式中的i标志。
m = re.match('[a-zA-Z]+', 'abcABC')
print(m)
# <re.Match object; span=(0, 6), match='abcABC'>
m = re.match('[a-z]+', 'abcABC', flags=re.IGNORECASE)
print(m)
# <re.Match object; span=(0, 6), match='abcABC'>
m = re.match('[A-Z]+', 'abcABC', flags=re.IGNORECASE)
print(m)
# <re.Match object; span=(0, 6), match='abcABC'>
你可以使用小于或等于。
- 内联旗帜
(?i)
- 缩略语
re.I
匹配每一行的开头和结尾。re.MULTILINE
^
该正则表达式中的元字符与字符串的开头相匹配。
默认情况下,只匹配整个字符串的开头,但下面也会匹配每一行的开头。相当于标准正则表达式中的m标志。re.MULTILINE
s = '''aaa-xxx
bbb-yyy
ccc-zzz'''
print(s)
# aaa-xxx
# bbb-yyy
# ccc-zzz
result = re.findall('[a-z]+', s)
print(result)
# ['aaa', 'xxx', 'bbb', 'yyy', 'ccc', 'zzz']
result = re.findall('^[a-z]+', s)
print(result)
# ['aaa']
result = re.findall('^[a-z]+', s, flags=re.MULTILINE)
print(result)
# ['aaa', 'bbb', 'ccc']
$
匹配字符串的结尾。默认情况下,只匹配整个字符串的结尾。re.MULTILINE
如果你指定这一点,它也将匹配每一行的结尾。
result = re.findall('[a-z]+$', s)
print(result)
# ['zzz']
result = re.findall('[a-z]+$', s, flags=re.MULTILINE)
print(result)
# ['xxx', 'yyy', 'zzz']
你可以使用小于或等于。
- 内联旗帜
(?m)
- 缩略语
re.M
指定多个标志
|
如果你想同时启用多个标志,请使用这个。在内联旗帜的情况下,每个字符后面必须有一个字母,如下图所示。(?am)
s = '''aaa-xxx
漢漢漢-字字字
bbb-zzz'''
print(s)
# aaa-xxx
# 漢漢漢-字字字
# bbb-zzz
result = re.findall(r'^\w+', s, flags=re.M)
print(result)
# ['aaa', '漢漢漢', 'bbb']
result = re.findall(r'^\w+', s, flags=re.M | re.A)
print(result)
# ['aaa', 'bbb']
result = re.findall(r'(?am)^\w+', s)
print(result)
# ['aaa', 'bbb']
贪婪和非贪婪的匹配
这是正则表达式的一个普遍问题,不仅仅是 Python 的问题,但我还是要写一下,因为它往往会让我陷入麻烦。
默认情况下,下面是一个贪婪的匹配,即匹配最长的字符串。
*
+
?
s = 'aaa@xxx.com, bbb@yyy.com'
m = re.match(r'.+com', s)
print(m)
# <re.Match object; span=(0, 24), match='aaa@xxx.com, bbb@yyy.com'>
print(m.group())
# aaa@xxx.com, bbb@yyy.com
后面的”?”将导致一个非贪婪的、最小的匹配,匹配尽可能短的字符串。
*?
+?
??
m = re.match(r'.+?com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
print(m.group())
# aaa@xxx.com
注意,默认的贪婪匹配可能会匹配到意外的字符串。