通俗易懂 Python 正则表达式
正则表达式, 也叫规则表达式, 是强大的文本字符串处理工具, 通常用来验证, 查找, 筛选, 提取, 替换那些符合某个规则的文本字符串,是实现文本高效处理的神器
1, 匹配规则
正则表达式的核心就是设计一个规则, 按照这个规则”按图索骥”, 去寻找符合这个规则的字符串, 并将它按需处理
先以一个最简单的例子进行探索:
re
: 正则表达式模块
findall
: 模块的一个方法, 传入 正则表达式 和 要去匹配字符串 将匹配结果以列表形式返回, 没有匹配结果返回空列表
\d
: 定义的规则, 表示匹配任意一个 0~9 的数字
198\d年
: 匹配符合 198某年
的字符串
然后按照规则去匹配字符串: '1988年 2000年 2020年 1980年'
# 导入模块 re
import re
# 按规则 r'198\d年' 匹配, r 的作用在 python 基础部分已介绍
re.findall(r'198\d年', '1988年 2000年 2020年 1980年')
['1988年', '1980年']
\d
是其中一个规则定义符, 可以和其他字符组合成正则表达式, 它自身也是一个正则表达式
d
则是字母 d 本身,像 \d
这样的特殊规则定义符有许多,如 .
,*
,+
,\s
,()
,[]
等等
任意一个非特殊字符都可以作为匹配它自身的正则表达式
如果要匹配规则定义符,例如如要匹配 \d
, 需要用 \
进行转义,也就是要用 \\d
来匹配 \d
(其实是 \\
来匹配 \
,d
来匹配 d
)
re.findall(r'\\d.+\[a]', r'a\d.+[a]')
['\\d.+[a]']
2, 常用规则定义符
2.01, 定义类别匹配
\w
匹配任意一个可以构成词语的 Unicode 字符, 包括数字及下划线, \W
则相反
a = r'my\wname'
b = r'my\Wname'
c = 'my1name, my_name, my.name, my我name'
re.findall(a, c)
['my1name', 'my_name', 'my我name']
re.findall(b, c)
['my.name']
\d
匹配任意一个 十进制 数字, \D
匹配任意一个非数字
a = r'01\d-\D123'
b = '010-0123, 010-o123, 010-P123'
re.findall(a, b)
['010-o123', '010-P123']
\s
匹配任何Unicode空白字符(包括 [ \t\n\r\f\v]
,还有很多其他字符,比如不同语言排版规则约定的不换行空格), \S
则相反
a = r'a\sb\Sc'
b = 'a b c, a bcc, a bcc'
re.findall(a, b)
['a bcc']
.
匹配除换行符 \n
之外的任意一个字符
a = r'a.b'
b = '''a
b, a-b, a b, a\nb'''
re.findall(a, b)
['a-b', 'a b']
\b
匹配 \w
和 \W
之间(或 \w
开头和结尾的边界)的空字符串, \B
与 \b
相反,匹配非 \w
和 \W
之间的空字符串(或 \W
开头和结尾的边界)
re.findall(r'\b.', 'ab啊_c。d,\n')
['a', '。', 'd', ',']
re.findall(r'.\B', 'ab啊_c。d,\n')
['a', 'b', '啊', '_', ',']
2.02, 定义范围匹配
用括号 []
将字符(表达式)包围起来, 表示在括号内指定的范围内匹配任意一个
re.findall('[abc]', 'bill')
['b']
在 []
内, 以 ^
开头, 表示排除括号内的字符(表达式)范围匹配
re.findall('[^abc]', 'abcd')
['d']
在 []
内, 数字或字母之间用 -
连接, 表示在两者(包含)之间的范围内匹配
re.findall('[a-d0-5A\-D]', 'af357AB-')
['a', '3', '5', 'A', '-']
一些特殊字符在 []
内失去特殊含义
re.findall('[(+*)\]\w]', '+(*)a].?')
['+', '(', '*', ')', 'a', ']']
2.03, 定义边界匹配
^
或 \A
, 表示必须以接下来的字符(表达式)开头才能被匹配, 换行开头也不能匹配
a = '^b\d[bc]'
b = '''a2b
b2b'''
c = 'b2bcd'
re.findall(a, b)
[]
re.findall(a, c)
['b2b']
a = '\Ab\d[bc]'
b = '''a2b
b2b'''
c = 'b2bcd'
re.findall(a, b)
[]
re.findall(a, c)
['b2b']
$
或 \Z
表示必须以其前面的字符(表达式)结尾才能被匹配, 换行之前的结束也不能匹配
re.findall('a\w$', '''ab
ac''')
['ac']
re.findall('a\w\Z', 'ab\nad')
['ad']
2.04, 定义数量匹配
+
其前面的字符(表达式)至少有一个的都能匹配
re.findall(r'10+', '110, 100, 1001')
['10', '100', '100']
?
其前面的字符(表达式)最多有一个的才能匹配
re.findall(r'10?', '1, 10, 100')
['1', '10', '10']
*
其前面的字符(表达式)没有或有多个都可以匹配
re.findall(r'10*', '1, 10, 1001')
['1', '10', '100', '1']
{n}
其前面的字符(表达式)有 n 个才能匹配
re.findall(r'1\d{3}', '12, 123, 1a23, 1234')
['1234']
{n,}
其前面的字符(表达式)有至少 n 个才能匹配, {m,n}
则表示有 m~n 个才能匹配, m 和 n 之间只有 ,
号而不能有空格
re.findall(r'1\d{2,}', '12, 123, 1234, 12345')
['123', '1234', '12345']
re.findall(r'1\d{2,3}', '12, 123, 1234, 12345')
['123', '1234', '1234']
2.05, 或
匹配符 |
符号 |
两边的内容, 有一边匹配 或 两边都匹配都可以, 但当 |
在括号 []
中则无此作用, 只代表它自身
re.findall(r'aa|bb', 'aacbbcaabbab')
['aa', 'bb', 'aa', 'bb']
re.findall(r'a[|]b', 'ab, a|b')
['a|b']
2.06, 定义组合匹配
用 ()
将多个字符组合成一个整体来匹配, 不管 ()
外是否有数量匹配符, 都只返回 ()
内的内容, 如果表达式内有多个 ()
封装的内容, 匹配结果以元组形式返回
# 匹配的是 ab 和 abab 但只返回括号内的 ab
re.findall(r'(ab)+', 'ab11abab')
['ab', 'ab']
re.findall(r'^(ab)+', 'ab11abab') # 必须 ab 开头
['ab']
# 匹配的是后面的 abab 返回元组
re.findall(r'(ab)(ab)', 'ab11abab')
[('ab', 'ab')]
将贪婪匹配转为非贪婪匹配
# 贪婪匹配 \d+ 使得无法将 000 取出
re.findall(r'(\d+)(0*)$', '123000')
[('123000', '')]
# + 后面加个 ? 号
re.findall(r'(\d+?)(0*)$', '123000')
[('123', '000')]
re.findall(r'[(ab)]', 'ab, (ab)')
['a', 'b', '(', 'a', 'b', ')']
如果 ()
内以 ?:
开头, 只有一组时,返回匹配的字符串,多组则 ?:
开头的不捕获不返回
re.findall(r'(?:ab)+', 'ab11abab')
['ab', 'abab']
# 匹配的是后面的 abab, 但只返回前一个 () 内的内容
re.findall(r'(ab)(?:ab)+', 'ab11abab')
['ab']
如果 ()
内以 ?=
开头, 则 ()
内的内容只是用来匹配, 不返回也不消耗匹配的字符, 也就是说, 匹配完了, 后面的字符需要继续匹配
re.findall(r'a(?=bc)bc', 'ab, abc, abcde')
['abc', 'abc']
如果 ()
内以 ?!
开头, 则 ()
内的内容只是用来排除, 也就是说, 排除括号内的内容都可以匹配, 不占字符, 匹配完了,
后面的字符需要继续匹配
# a 后面除了 bc 都可以匹配
re.findall(r'a(?!bc)\w+', 'abc, acb, abde')
['acb', 'abde']
\number
匹配指定的组合,组合从 1 开始编号
# \1 匹配第一个组合 (a)
re.findall(r'(a)(\d+)(\1)', 'aba1aa34a2')
[('a', '1', 'a'), ('a', '34', 'a')]
绝大部分 Python 的标准转义字符也被正则表达式分析器支持:
\a \b \f \n
\N \r \t \u
\U \v \x \\
(注意 \b
被用于表示词语的边界,它只在 []
内表示退格,比如 [\b]
)
2.07, 匹配规则修改标志
介绍几个常用的规则修改标志
re.I
大小写不敏感匹配
re.findall(r'aA','aA, aa', re.I)
['aA', 'aa']
re.S
使 .
匹配任何字符, 包括换行符
s = '''a
b, a-b'''
re.findall(r'a.b', s, re.S)
['a\nb', 'a-b']
re.M
使得 ^
和 $
能匹配换行的开始或换行前的结束
a = '^b\d[bc]'
b = '''b2c
b2b'''
re.findall(a, b, re.M)
['b2c', 'b2b']
a = 'a\d[bc]$'
b = '''a2b
a2c'''
re.findall(a, b, re.M)
['a2b', 'a2c']
3, 匹配和处理方法
match
从字符串起始位置匹配, 匹配到的字符返回为 match 对象, 可以用方法加以获取
a = re.match(r'ab', 'abc')
b = re.match(r'a(bc)d(ef)g', 'abcdefgh')
c = re.match(r'ab', 'bab')
a
<re.Match object; span=(0, 2), match='ab'>
b
<re.Match object; span=(0, 7), match='abcdefg'>
c
# group() 获取匹配的全部字符,
# group(1) 获取 () 内匹配的第一个, 以此类推
a.group(), b.group(), b.group(1), b.group(2)
('ab', 'abcdefg', 'bc', 'ef')
# start 获取匹配开始的位置
a.start(), b.start()
(0, 0)
# end 获取匹配结束的位置
a.end(), b.end()
(2, 7)
# span 获取 (开始, 结束) 元组
a.span(), b.span()
((0, 2), (0, 7))
compile
编译正则表达式, 返回一个 Pattern 对象(后面简写为p), 然后可以用该对象调用方法
p = re.compile('\d+')
p
re.compile(r'\d+', re.UNICODE)
# 调用上面的 match 方法, 可以指定开始和结束位置
a = p.match('12a21', 3)
a
<re.Match object; span=(3, 5), match='21'>
re.search
扫描整个字符串, 第一个匹配的字符串返回为 match 对象
p.search
扫描指定范围, 不指定就全部扫描
r = 'python\d.\d'
s = 'python3.0, python3.8'
p = re.compile(r)
a = re.search(r, s)
b = p.search(s)
c = p.search(s, 1)
a
<re.Match object; span=(0, 9), match='python3.0'>
b
<re.Match object; span=(0, 9), match='python3.0'>
c
<re.Match object; span=(11, 20), match='python3.8'>
sub
替换字符串中的匹配项, 可以控制替换次数, 还可传入函数高级替换
r = '[,.]'
s = '去掉,逗号和.句号.'
p = re.compile(r)
re.sub(p, '', s)
'去掉逗号和句号'
p.sub('', s, 1)
'去掉逗号和.句号.'
findall
已经介绍过
r = r'\D+(\d+)\D+(\d+)\D+(\d+)*'
s = '分组提取123所有456数字78.'
p = re.compile(r)
re.findall(p, s)
[('123', '456', '78')]
p.findall(s, 8)
[('456', '78', '')]
finditer
和 findall
类似, 只是返回的是一个 match 迭代器
import re
r = r'\D+(\d+)\D+(\d+)\D+(\d+)*'
s = '分组提取123所有456数字78.'
p = re.compile(r)
a = re.finditer(p, s)
b = p.finditer(s, 8)
a
<callable_iterator at 0x18d4fa31460>
b
<callable_iterator at 0x18d4fa31430>
for i, j in zip(a, b):
print(i)
print(j)
print(i.group(1, 2, 3))
print(j.group(1, 2, 3))
<re.Match object; span=(0, 16), match='分组提取123所有456数字78'>
<re.Match object; span=(8, 17), match='有456数字78.'>
('123', '456', '78')
('456', '78', None)
split
用匹配到的字符(或字符串), 将整个字符串分割返回列表, 可设置最大拆分数
r = '[\s\,-]'
s = '将,每 个-字 拆,开'
p = re.compile(r)
re.split(p, s)
['将', '每', '个', '字', '拆', '开']
p.split(s, 2)
['将', '每', '个-字 拆,开']