Python正则表达式与re模块的使用
1. 前言
正则表达式(regex)是由一系列普通字符(如字母、数字等)和特殊字符(称为“元字符”)组成的模式,用于描述要匹配的文本模式,因此正则表达式可以作为一种匹配和操作文本的强大工具。它可以在文本中查找、替换、提取和验证特定模式。
构造正则表达式的方法与构造数学表达式的方法一样,即用多种元字符与运算符将小的表达式结合在一起来创建更大的表达式。正则表达式的组件可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合。
2. 正则表达式
2.1 匹配单个字符的方法
缩写字符分类 | 表示 |
---|---|
. | 匹配任意1个字符(除了\n) |
[] | 匹配方括号中列举的字符 |
\d | 匹配数字,即0-9 |
\D | 匹配除0-9的数字以外的任何字符 |
\s | 匹配空格、制表符或换行符(可认为是匹配“空白”字符) |
\S | 匹配除空格、制表符和换行符以外的任何字符 |
\w | 匹配任何字母、数字或下划线字符(可认为是匹配“单词”字符) |
\W | 匹配除字母、数字和下划线以外的任何字符 |
2.2 匹配多个字符的方法
首先介绍括号在正则表达式中的作用。括号(“()”)用于在正则表达式中创建“分组”。若需要在文本中匹配括号,需要用反斜杠(\)对括号进行字符转义。
其次介绍管道符号在正则表达式中的作用。管道符号(“|”)用于在正则表达式中匹配其左右两个模式中的一个,也可以通过使用多个管道符号来匹配多个模式中的一个,可理解为选择作用。
字符 | 功能 | 位置 |
---|---|---|
* | 匹配前一个字符出现0次或多次,即可有可无 | 用于字符或括号分组的后方 |
+ | 匹配前一个字符出现1次或多次,即至少出现1次 | 用于字符或括号分组的后方 |
? | 匹配前一个字符出现0次或1次 | 用于字符或括号分组的后方 |
{m} | 匹配前一个字符出现m次 | 用于字符或括号分组的后方 |
{m, n} | 匹配前一个字符出现m到n次 | 用于字符或括号分组的后方 |
2.3 匹配开头结尾
字符 | 功能 | 位置 |
---|---|---|
^ | 匹配字符串开头 | 用于字符或括号分组的前方 |
$ | 匹配字符串结尾 | 用于字符或括号分组的后方 |
3. Python中的re模块-基础
Python中的所有正则表达式函数都在re模块中。输入以下代码以导入该模块:
import re
要通过re模块使用正则表达式对字符串进行查找、替换、提取或验证有两种实现方法:一种是先创建Regex对象,再对该对象使用re库中定义的各种方法;另一种是直接调用re库中的函数,传入正则表达式和字符串,函数返回值即为操作的结果。下面将分别介绍两种方式:
3.1 方法一:创建Regex对象
3.1.1 re.compile函数
向re.compile函数中传入一个字符串,表示正则表达式,它将返回一个Regex模式对象(或简称“Regex对象”)。
本文以北美电话号码模式为例。若要创建一个Regex对象来匹配电话号码模式,可用以下代码:
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
3.1.2 search()方法
Regex对象的search()方法查找传入的字符串,以寻找该正则表达式的所有匹配。如果字符串中没有找到该正则表达式模式,则返回None。如果找到了该模式,返回一个Match对象。Match对象有一个group()方法,返回被查找字符串中实际匹配的文本。
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
result = phoneNumRegex.search('My phone number is 415-555-4242')
print(result.group())
运行结果:
415-555-4242
3.1.3 match()方法
Regex对象的match()方法从头开始匹配传入的字符串。如果不是起始位置匹配成功的话,match()方法将返回None;否则返回匹配成功的Match对象。
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
result1 = phoneNumRegex.match('415-555-4242 is my phone number')
result2 = phoneNumRegex.match('My phone number is 415-555-4242')
print(result1.group())
print(result2) # 由于None值没有group()方法,故直接输出
运行结果:
415-555-4242
None
注意match()方法与search()方法的区别:match()方法只匹配字符串的开始,如果字符串开始不符合正则表达式的模式,则匹配失败,返回None;而search()方法匹配整个字符串,直到找到一个匹配为止,返回Match对象,如果整个字符串都没有满足正则表达式模式的子串,则匹配失败,返回None。
3.1.4 findall()方法
Regex对象的findall()方法将在字符串中找到正则表达式所匹配的所有子串,并返回一个列表;如果有多个匹配模式,则返回元组列表;如果没有找到匹配的子串,则返回空列表。
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
result = phoneNumRegex.findall("My phone number is 415-555-4242, and my friend's phone number is 514-555-2424")
print(result)
运行结果:
['415-555-4242', '514-555-2424']
3.1.5 finditer()方法
finditer()方法与findall()方法类似,它将在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
iteration = phoneNumRegex.finditer("My phone number is 415-555-4242, and my friend's phone number is 514-555-2424")
for match in iteration:
print(match.group())
运行结果:
415-555-4242
514-555-2424
3.1.6 sub()方法
Regex对象的sub()方法可以用新的文本替换掉正则表达式所表示的模式。该方法需要传入两个参数:第一个参数是一个字符串,用于替换发现的匹配;第二个参数是一个字符串,即待替换的原字符串。sub()方法返回替换完成后的字符串。
namesRegex = re.compile(r'Agent \w+')
result = namesRegex.sub('CENSORED', 'Agent Alice gave the secret documents to Agent Bob.')
print(result)
运行结果:
CENSORED gave the secret documents to CENSORED.
在sub()方法的第一个参数中,可以输入\1、\2、\3,表示“在替换中输入分组1、2、3的文本”。通过这种方法可以保留部分被替换子串中的内容。
agentNamesRegex = re.compile(r'Agent (\w)(\w*)')
result1 = agentNamesRegex.sub(r'\1****', 'Agent Alice told Agent Carol that Agent Eve knew Agent Bob was a double agent.')
result2 = agentNamesRegex.sub(r'*\2', 'Agent Alice told Agent Carol that Agent Eve knew Agent Bob was a double agent.')
print(result1)
print(result2)
运行结果:
A**** told C**** that E**** knew B**** was a double agent.
*lice told *arol that *ve knew *ob was a double agent.
3.1.7 subn()方法
subn()方法与sub()方法类似,它将用新的文本替换掉正则表达式所表达的模式,并返回一个元组(字符串,替换次数)。
agentNamesRegex = re.compile(r'Agent (\w)(\w*)')
result = agentNamesRegex.subn(r'\1****', 'Agent Alice told Agent Carol that Agent Eve knew Agent Bob was a double agent.')
print(result)
运行结果:
('A**** told C**** that E**** knew B**** was a double agent.', 4)
3.1.8 split()方法
Regex对象的split()方法按照能够匹配的子串将字符串分割后返回列表。该方法需要传入两个参数:第一个参数是一个字符串,即待分割的原字符串;第二个参数maxsplit,即最大分割次数,maxsplit默认为0,即不限制次数。
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
result1 = phoneNumRegex.split("My phone number is 415-555-4242, and 514-555-2424 is my friend's phone number")
result2 = phoneNumRegex.split("My phone number is 415-555-4242, and 514-555-2424 is my friend's phone number", maxsplit=1)
print(result1)
print(result2)
运行结果:
['My phone number is ', ', and ', " is my friend's phone number"]
['My phone number is ', ", and 514-555-2424 is my friend's phone number"]
3.2 方法二:直接调用re库函数
re库中函数的使用方法与上述各种方法类似,由于上述各种方法是经compile函数编译为Regex对象后使用的,所以不需要在方法中传入正则表达式相关信息;而re库函数在使用时是直接调用,所以需要一个额外参数指定正则表达式表示的模式。这个参数一般是re库函数的第一个参数,后面参数的位置则依次顺延。下面我们仍举例说明各种函数的使用方法。
3.2.1 search()函数
result = re.search(r'\d\d\d-\d\d\d-\d\d\d\d', 'My phone number is 415-555-4242')
print(result.group())
运行结果:
415-555-4242
3.2.2 match()函数
result1 = re.match(r'\d\d\d-\d\d\d-\d\d\d\d', '415-555-4242 is my phone number')
result2 = re.match(r'\d\d\d-\d\d\d-\d\d\d\d', 'My phone number is 415-555-4242')
print(result1.group())
print(result2)
运行结果:
415-555-4242
None
3.2.3 findall()函数
result = re.findall(r'\d\d\d-\d\d\d-\d\d\d\d', "My phone number is 415-555-4242, and my friend's phone number is 514-555-2424")
print(result)
运行结果:
['415-555-4242', '514-555-2424']
3.2.4 finditer()函数
iteration = re.finditer(r'\d\d\d-\d\d\d-\d\d\d\d', "My phone number is 415-555-4242, and my friend's phone number is 514-555-2424")
for match in iteration:
print(match.group())
运行结果:
415-555-4242
514-555-2424
3.2.5 sub()函数
result = re.sub(r'Agent \w+', 'CENSORED', 'Agent Alice gave the secret documents to Agent Bob.')
print(result)
运行结果:
CENSORED gave the secret documents to CENSORED.
result = re.sub(r'Agent (\w)(\w*)', r'\1****', 'Agent Alice told Agent Carol that Agent Eve knew Agent Bob was a double agent.')
print(result)
运行结果:
A**** told C**** that E**** knew B**** was a double agent.
3.2.6 subn()函数
result = re.subn(r'Agent (\w)(\w*)', r'\1****', 'Agent Alice told Agent Carol that Agent Eve knew Agent Bob was a double agent.')
print(result)
运行结果:
('A**** told C**** that E**** knew B**** was a double agent.', 4)
3.2.7 split()函数
result = re.split(r'\d\d\d-\d\d\d-\d\d\d\d', "My phone number is 415-555-4242, and 514-555-2424 is my friend's phone number")
print(result)
运行结果:
['My phone number is ', ', and ', " is my friend's phone number"]
4.Python中的re模块-进阶
4.1 括号分组与groups()方法
由2.2节可知,使用括号可对正则表达式的字符串分组,第一对括号中的字符串是第一组,第二对括号中的字符串是第二组,以此类推。向group()方法中传入正整数,可获得匹配文本的不同部分;向group方法传入0或不传入参数,将返回整个匹配的文本。
phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)')
result = phoneNumRegex.search('My phone number is 415-555-4242')
print(result.group())
print(result.group(0))
print(result.group(1))
print(result.group(2))
运行结果:
415-555-4242
415-555-4242
415
555-4242
注:group()方法中传入的参数的含义是“第几个”,从1开始,请不要和数组、列表索引等从0开始的数列混淆。
若想要一次获取所有分组,可使用groups()方法,注意函数名的复数形式。
phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)')
result = phoneNumRegex.search('My phone number is 415-555-4242')
areaCode, mainNumber = result.groups()
print(result.groups())
print(areaCode)
print(mainNumber)
运行结果:
('415', '555-4242')
415
555-4242
4.2 贪心与非贪心匹配
Python的正则表达式默认是贪心的,即若有多种匹配方式,正则表达式会尽可能匹配最长的字符串;非贪心则相反,若有多种匹配方式,正则表达式会尽可能匹配最短的字符串,这时需要在“*”、“?”、“+”或“{m,n}”后面加上“?”,使默认的贪心匹配变为非贪心匹配。
通俗理解:
字符 | 功能 |
---|---|
* | 匹配前一个字符出现0次或多次,匹配尽量多 |
*? | 匹配前一个字符出现0次或多次,匹配尽量少 |
? | 匹配前一个字符出现0次或1次,匹配尽量多 |
?? | 匹配前一个字符出现0次或1次,匹配尽量少 |
+ | 匹配前一个字符出现1次或多次,匹配尽量多 |
+? | 匹配前一个字符出现1次或多次,匹配尽量少 |
{m,n} | 匹配前一个字符出现m次到n次,匹配尽量多 |
{m,n}? | 匹配前一个字符出现m次到n次,匹配尽量少 |
greedyHaRegex = re.compile(r'(Ha){3,5}')
nongreedyHaRegex = re.compile(r'(Ha){3,5}?')
result1 = greedyHaRegex.search('HaHaHaHaHaHa')
result2 = nongreedyHaRegex.search('HaHaHaHaHaHa')
print(result1.group())
print(result2.group())
运行结果:
HaHaHaHaHa
HaHaHa
4.3 flags参数
在Regex对象的各种方法与re库的函数中有一个默认为0的参数,即flags=0。它一般是函数或方法的最后一个参数,被称为标志位,用于控制正则表达式的匹配方式,如:是否区分大小写、多行匹配等。多个标志位可以通过按位或(|)来指定,如re.I|re.M被设置成I和M标志。常见的标志位与含义见下表:
标志位 | 含义 |
---|---|
re.I | 使匹配对大小写不敏感 |
re.L | 做本地化识别(locale-aware)匹配 |
re.M | 多行匹配,影响^和$ |
re.S | 使“.”匹配包括换行在内的所有字符 |
re.U | 根据Unicode字符集解析字符,这个标志影响\w, \W |
re.X | 为了增加可读性,忽略空格和“#“后面的注释 |
robocop = re.compile('robocop', re.I)
result1 = robocop.search('RoboCop is a part man, part machine, all cop.')
result2 = robocop.search('ROBOCOP protects the innocent.')
result3 = robocop.search('Al, why does your blog talk about robocop so much?')
print(result1.group())
print(result2.group())
print(result3.group())
运行结果:
RoboCop
ROBOCOP
robocop
5. 后记
由于正则表达式系统较为复杂,博主平时需要使用时经常忘记某部分使用方法,网上的其他博客又无法及时解决问题,所以在查阅了多本书籍、多篇教程后写出此博客,希望能帮到想要学习正则表达式的朋友、学过正则表达式但又忘记的朋友。:happy::happy::happy:
由于实验作业考试等原因,博主在创作本篇博客时心态比较着急,若有谬误或不完善的地方,欢迎在评论区或私信指正。:happy::happy::happy: