正则表达式(Regular Expression, RE), 又称规则表达式, 通常被用来检索和替换那些符合某些模式的字符串.
正则表达式语法
在处理字符串时, 通常会涉及查找符合某些复杂规则的字符串. 正则表达式是一种用来描述这些规则的语言, 它可以用来匹配, 搜索和替换文本.
换句话说, 正则表达式是记录文本规则的代码.
行定位符
行定位符就是用来描述字符串的边界.^
和$
分别用来匹配字符串的开头和结尾.
例如, ^abc
可以匹配以”abc”开头的字符串, $abc
可以匹配以”abc”结尾的字符串, abc
可以匹配字符串中包含”abc”的部分, 而^abc$
可以匹配整个字符串”abc”.
元字符
元字符是用来描述字符集合的特殊字符. 常见的元字符有:
.
匹配除换行符以外的任意字符\w
匹配字母,数字,下划线或汉字\s
匹配空白字符, 包括空格,制表符,换行符等\d
匹配数字\b
匹配单词的边界^
匹配字符串的开头$
匹配字符串的结尾
^\d
表示必须以数字开头.
\d$
表示必须以数字结束.
限定符
限定符用来指定正则表达式的匹配次数.
?
匹配前面的字符0次或1次——例如colou?r
可以匹配”color”或”colour”.*
匹配前面的字符0次或多次——例如go*gle
可以匹配”ggle”,”gogle”,”google”, …+
匹配前面的字符1次或多次——例如go+gle
可以匹配”gogle”, “google”, “gooogle”, …{n}
匹配前面的字符恰好n次——例如go{2}gle
只匹配”google”.{n,}
匹配前面的字符至少n次——例如go{2,}gle
可以匹配”google”, “gooogle”, …{n,m}
匹配前面的字符至少n次, 但不超过m次——例如go{2,4}gle
可以匹配”google”, “gooogle”, “goooogle”
\d{3}
表示匹配3个数字.
\s+
表示至少有一个空格.
字符类
使用正则表达式查找数字和字母很简单, 但是如果要匹配没有预定义的字符集合, 或者要匹配特定的字符组合, 就需要使用字符类.
字符类是用方括号[]
括起来的一组字符, 用来匹配指定范围内的字符.
例如[aeiou]匹配任何一个元音字母, [0-9]匹配任何数字, [a-zA-Z]匹配任何字母.
更进阶的,[0-9a-zA-Z\_]
可以匹配一个数字, 字母或者下划线;
[0-9a-zA-Z\_]+
可以匹配至少由一个数字, 字母或者下划线组成的字符串, 比如’a100′, ‘0_Z’, ‘Py3000’等等;
[a-zA-Z\_][0-9a-zA-Z\_]*
可以匹配由字母或下划线开头, 后接任意个由一个数字, 字母或者下划线组成的字符串, 也就是Python合法的变量;
[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}
更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)
排除字符
如果要匹配除了某些字符以外的字符, 可以在字符类前加上^
来排除这些字符.
例如[^aeiou]可以匹配除了元音字母以外的任意字符.
选择字符
如果要匹配多个字符中的一个, 可以使用|
来分隔这些字符.
例如(^\d{17})(\d|X|x)$
可以匹配身份证号码.
转义字符
如果要匹配元字符本身, 就需要使用\
来转义.
例如\d
匹配数字, \s
匹配空白字符, \.
匹配点号.
分组
()的第一个作用是创建分组(子表达式), 第二个作用是提取分组匹配的文本.
例如(\.[0-9]{1,3}){3}
就是对分组\.[0-9]{1,3}
重复3次, 然后提取匹配的文本.
而(thir|four)th可以匹配”third”或”fourth”, 如果去掉括号, 则匹配”thir”或”fourth”.
在Python中使用正则表达式
在Python中使用正则表达式, 是将其作为模式字符串使用的.
例如将匹配一个非字母字符的正则表达式转化为模式字符串可以使用以下代码:
'[^a-zA-Z]'
而如果将匹配字母m开头的单词的正则表达式转换为模式字符串, 则不能直接在其两侧添加引号, 如下:
'\bm\w*\b'
是错误的, 应该将其中的\
转义为\\
, 如下:
'\\bm\\w*\\b'
但是模式字符串中有很多需要转义的字符, 因此在实际使用中, 建议改写为原生字符串, 即在引号前加上r
, 如下:
r'\bm\w*\b'
为了方便, 我们接下来都将使用原生字符串.
使用re模块实现正则表达式操作
Python的re模块提供了对正则表达式的支持, 包括模式匹配, 替换, 分割等操作.
在实现时, 可以使用re模块提供的方法进行字符串处理, 也可以将模式字符串转换为正则表达式, 再使用正则表达式对象的相关方法进行字符串处理.
在使用re模块时, 我们应该先应用import语句将其导入(模块我们也会在后面介绍).
import re
匹配字符串
匹配字符串可以使用re模块提供的match(),search(),findall()等方法.
match()方法
match()方法从字符串的开头开始匹配模式, 如果匹配成功, 则返回一个Match对象, 否则返回None.即使字符串中有多个符合正则表达式的子字符串, re.match()也只会返回第一个匹配结果.
语法格式如下:
re.match(pattern, string, [flags])
- pattern: 正则表达式的模式字符串, 由要匹配的正则表达式转换而来
- string: 要匹配的字符串
- flags: 可选参数, 用于控制正则表达式的匹配方式
- A或ASCII: 对于\w, \W, \b, \B, \d, \D, \s, \S只进行ASCII匹配;
- I或IGNORECASE: 忽略大小写;
- M或MULTILINE: 多行模式, 改变’^’和’$’的行为, 使它们分别匹配字符串的开头和结尾, 以及每一行的开头和结尾;
- S或DOTALL: 点(.)匹配所有字符, 包括换行符;
- X或VERBOSE: 详细模式, 允许使用注释, 空格和换行符来提高可读性.
例如匹配字符串是否以”mr_”开头, 其中不区分大小写, 可以使用如下代码:
import re
pattern=r'mr_\w+'
string='Mr_John_Doe mr_jane_smith'
match=re.match(pattern, string, re.IGNORECASE)
print(match)
string='项目 Mr_John_Doe mr_jane_smith 已完成'
match=re.match(pattern, string, re.IGNORECASE)
print(match)
输出结果:
<re.Match object; span=(0, 11), match='Mr_John_Doe'>
None
由此可见, 字符串”Mr_John_Doe”的开头是”mr_”的大小写形式, 因此匹配成功. 而字符串”项目 Mr_John_Doe 已完成”的开头是”项目 “的大小写形式, 因此匹配失败.
Match对象包含了匹配值的位置和匹配数据, 其中:
- 要获得匹配值的起始位置, 可以使用Match对象的start()方法;
- 要获得匹配值的结束位置, 可以使用Match对象的end()方法.
- 要返回匹配位置的元组, 可以使用Match对象的span()方法.
- 要返回匹配的字符串, 可以使用Match对象的strings属性.
- 要返回匹配的数据, 可以使用Match对象的group()方法.
这些标志可以单独使用,也可以通过按位或|
组合使用.例如, re.IGNORECASE | re.MULTILINE
表示同时启用忽略大小写和多行模式
import re
pattern=r'mr_\w+'
string='Mr_John_Doe mr_jane_smith'
match=re.match(pattern, string, re.IGNORECASE)
print('匹配值的起始位置:', match.start())
print('匹配值的结束位置:', match.end())
print('匹配位置的元组:', match.span())
print('匹配的字符串:', match.string)
print('匹配的数据:', match.group())
输出结果:
匹配值的起始位置: 0
匹配值的结束位置: 11
匹配位置的元组: (0, 11)
匹配的字符串: Mr_John_Doe mr_jane_smith
匹配的数据: Mr_John_Doe
search()方法
search()方法从字符串的任意位置开始匹配模式, 如果匹配成功, 则返回一个Match对象, 否则返回None. 也只会返回第一个匹配结果.
语法格式如下:
re.search(pattern, string, [flags])
- pattern: 正则表达式的模式字符串, 由要匹配的正则表达式转换而来
- string: 要匹配的字符串
- flags: 可选参数, 用于控制正则表达式的匹配方式, 同match()方法.
例如匹配字符串是否以”mr_”开头, 其中不区分大小写, 可以使用如下代码:
import re
pattern=r'mr_\w+'
string='Mr_John_Doe mr_jane_smith'
match=re.search(pattern, string, re.IGNORECASE)
print(match)
string='项目 Mr_John_Doe mr_jane_smith 已完成'
match=re.search(pattern, string, re.IGNORECASE)
print(match)
输出结果:
<re.Match object; span=(0, 11), match='Mr_John_Doe'>
<re.Match object; span=(3, 14), match='Mr_John_Doe'>
由此可见, search()方法可以匹配字符串中任意位置的”mr_”开头的子字符串.
findall()方法
findall()方法在字符串中找到所有匹配正则表达式的子字符串, 并返回一个列表, 如果没有找到匹配的子字符串, 则返回一个空列表.
语法格式如下:
re.findall(pattern, string, [flags])
- pattern: 正则表达式的模式字符串, 由要匹配的正则表达式转换而来
- string: 要匹配的字符串
- flags: 可选参数, 用于控制正则表达式的匹配方式, 同match()方法.
例如搜索字符串中所有以”mr_”开头的子字符串, 其中不区分大小写, 可以使用如下代码:
import re
pattern=r'mr_\w+'
string=' Mr_John_Doe mr_jane_smith'
matches=re.findall(pattern, string, re.I)
print(matches)
string='项目 Mr_John_Doe mr_jane_smith 已完成'
matches=re.findall(pattern, string) #区分大小写
print(matches)
输出结果:
['Mr_John_Doe', 'mr_jane_smith']
['mr_jane_smith']
分组
除了简单地判断是否匹配之外, 正则表达式还有提取子串的强大功能. 用()表示的就是要提取的分组(Group).
^(\d{3})-(\d{3,8})$
分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码.
如果正则表达式中定义了组, 就可以在Match对象上用group()方法提取出子串来.
我们可以使用 group(num) 或 groups() 匹配对象函数来获取匹配表达式.
- group(num) 方法返回第 num 个分组匹配的字符串, 如果 num 为 0, 则返回整个匹配的字符串.
- groups() 方法返回一个包含所有分组匹配的字符串的元组.
import re
pattern=r'^(\d{3})-(\d{3,8})$'
string='010-12345'
match=re.match(pattern, string)
print(match.group(1)) #输出区号010
print(match.group(2)) #输出本地号码12345
print(match.groups()) #输出('010', '12345')
替换字符串
替换字符串可以使用re模块提供的sub(),subn()等方法.
sub()方法
sub()方法用于替换字符串中所有匹配正则表达式的子字符串, 返回替换后的字符串.
语法格式如下:
re.sub(pattern, repl, string, [count], [flags])
- pattern: 正则表达式的模式字符串, 由要匹配的正则表达式转换而来
- repl: 替换字符串, 可以是一个字符串也可以是一个函数
- string: 要匹配的字符串
- count: 可选参数, 用于指定最多替换的次数, 默认为0, 表示全部替换.
- flags: 可选参数, 用于控制正则表达式的匹配方式, 同match()方法.
例如隐藏字符串中的电话号码, 可以使用如下代码:
import re
pattern=r'-\d{8}'
string='我的电话号码是010-12345678'
new_string=re.sub(pattern, '-*******', string)
print(new_string) #输出'我的电话号码是010-*******'
subn()方法
subn()方法与sub()方法类似, 但它返回一个元组, 包含替换后的字符串和替换的次数.
import re
pattern=r'-\d{8}'
string='我的电话号码是010-12345678'
new_string, count=re.subn(pattern, '-*******', string)
print(new_string) #输出'我的电话号码是010-*******'
print(count) #输出1
分割字符串
分割字符串可以使用re模块提供的split()方法.split()方法将字符串按照正则表达式匹配到的子字符串进行分割, 返回一个列表.
语法格式如下:
re.split(pattern, string, [maxsplit], [flags])
- pattern: 正则表达式的模式字符串, 由要匹配的正则表达式转换而来
- string: 要匹配的字符串
- maxsplit: 可选参数, 用于指定最多分割次数, 默认为0, 表示全部分割.
- flags: 可选参数, 用于控制正则表达式的匹配方式, 同match()方法.
例如@多个好友后想要提取出好友的名字, 可以使用如下代码:
import re
pattern = r'@(\w+)'
string = '@shiqi @wztxy @Iris @Arlan'
list1 = re.split(pattern, string)
print(list1) #输出['', 'shiqi', ' ', 'wztxy', ' ', 'Iris', ' ', 'Arlan', '']
for item in list1:
if item != ' ':
print(item) #输出shiqi wztxy Iris Arlan
编译
如果要重复使用同一个正则表达式, 我们可以先将其编译成正则表达式对象, 然后使用该对象的方法进行字符串处理.
compile 函数用于编译正则表达式, 生成一个正则表达式(Pattern)对象, 供 match() 和 search() 这两个函数使用.
语法格式如下:
re.compile(pattern [,flags])
- pattern: 正则表达式的模式字符串, 由要匹配的正则表达式转换而来
- flags: 可选参数, 用于控制正则表达式的匹配方式, 同match()方法.
import re
pattern = r'(\d{3})-(\d{3,8})'
string = '010-12345'
pattern_obj = re.compile(pattern)
match = pattern_obj.match(string)
print(match.group(1)) #输出区号010
print(match.group(2)) #输出本地号码12345