通俗易懂 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', '')]

finditerfindall类似, 只是返回的是一个 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)  
['将', '每', '个-字 拆,开']
jupyter附件