1.1 基础语法
(1) 常用元字符
语法 | 描述 |
---|---|
\b | 匹配单词的开始或结束 |
\d | 匹配数字 |
\s | 匹配任意不可见(空格、换行符、制表符等等),等价于[\f\n\r\t\v]. |
\w | 匹配数字任意Unicode字符集,包括字母、数字、下划线、汉字等 |
. | 匹配除换行符(\n)以外的任意字符 |
^或\A | 匹配字符串或行的起始位置 |
$或\Z | 匹配字符串或行的结束位置 |
(2) 限定词(又叫量词)
语法 | 描述 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或者更多次 |
{n,m} | 重复n到m次 |
(3) 常用反义词
语法 | 描述 |
---|---|
\B | 匹配非单词的开始或结束 |
\D | 匹配非数字 |
\S | 匹配任意可见的字符,[^\f\n\r\t\v] |
\W | 匹配任意非Unicode字符集 |
[^abc] | 除a、b、c以外的任意字符 |
(4) 字符族
语法 | 描述 |
---|---|
[abc] | a、b或c |
[^abc] | 除a、b、c以外的任意字符 |
[a-zA-Z] | a到z或A到Z |
[a-d[m-p]] | a到d或m到p,即a-dm-p |
[a-z&&[def]] | d、e或f(交集) |
[a-z&&[^bc]] | a到z,除了b和c:ad-z |
[a-z&&[^m-p]] | a到z,减去m到p:a-lq-z |
以上便是正则的基础内容,下面来写两个例子看下:
s='123abc你好'
re.search('\d',s).group()
re.search('\w+',s).group()
结果
123
123abc你好
1.2 修饰符
修饰符在各语言中也是有差异的python
中的修饰符
语法 | 描述 |
---|---|
re.A | 匹配ASCLL字符类,影响\W,\w,\b,\B,\d,\D |
re.l | 忽略大小写 |
re.L | 做本地化识别匹配(这个极少极少使用) |
re.M | 多行匹配,影响^和$ |
re.S | 使.匹配包括换行符(\n)在内的所有字符 |
re.U | 匹配Unicode字符集。与re.A相对,这是默认设置 |
re.X | 忽略空格和#后面的注释以获得看起来更易懂的正则 |
(1) re.A
修饰符A
使\w
只匹配ASCLL字符,\W
匹配非ASCLL字符。
s='123abc你好'
re.search('\w+',S,re.A).group()
re.search('\W+',S,re.A).group()
结果
123abc
你好
但是描述中还有\d
和\D
,数字不都是ASCLL字符吗?这是什么意思?别忘了,还有全角和半角!
s='0123456789'#全角数字
re.search('\d+',s,re.U).group()
结果
0123456789
(2) re.M
多行匹配的模式其实也不常用,很少有一行行规则整的数据
s='aaa\r\nbbb\r\nccc'
re.findall('^[\s\w]*?$',s)
re.findall('^[\s\w]*?$',s,re.M)
结果
['aaa\r\nbbb\r\nccc'] #单行模式
['aaa\r','bbb\r','ccc'] #多行模式
(3) re.S
这个简单,直接看例子
s='aaa\r\nbbb\r\nccc'
re.findall('^.*',s)
re.findall('^.*',s,re.S)
结果
['aaa\r']
['aaa\r\n\bbb\r\nccc']
(4)re.X
用法如下:
rc = re.compile(r"""
\d+ # 匹配数字
# 和字母
[a-zA-Z]+
""", re.X)
rc.search('123abc').group()
结果:
123abc
注意,用了 X 修饰符后,正则中的所有空格会被忽略,包括正则里面的原本有用的空格。如果正则中有需要使用空格,只能用 \s
代替。
(5)(?aiLmsux)
修饰符不仅可以代码中指定,也可以在正则中指定。(?aiLmsux) 表示了以上所有的修饰符,具体用的时候需要哪个就在 ? 后面加上对应的字母,示例如下,
(?a) 和 re.A 效果是一样的:
s = '123abc你好'
re.search('(?a)\w+', s).group()
re.search('\w+', s, re.A).group()
结果是一样的:
123abc
123abc
1.3、贪婪与懒惰
当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。
s = 'aabab'
re.search('a.*b', s).group() # 这就是贪婪
re.search('a.*?b', s).group() # 这就是懒惰
结果:
aabab
aab
简单来说:
- 所谓贪婪,就是尽可能 多 的匹配;
- 所谓懒惰,就是尽可能 少 的匹配。
- *、+、{n,} 这些表达式属于贪婪;
- *?、+?、{n,}? 这些表达式就是懒惰(在贪婪的基础上加上 ?)。
2、正则进阶
2.1、捕获分组
语法 | 描述 |
---|---|
(exp) | 匹配exp,并捕获文本到自动命名的组里 |
(?Pexp) | 匹配exp,并捕获文本到名称为name的组里 |
(?:exp) | 匹配exp,不捕获匹配的文本,也不给此分组分配组号 |
(?P=name) | 匹配之前由名为name的组匹配的文本 |
注意:在其他语言或者网上的一些正则工具中,分组命名的语法是 (?exp) 或 (?'name'exp) ,但在 Python 里,这样写会报错:This named group syntax is not supported in this regex dialect。Python 中正确的写法是: (?Pexp)
示例一:
分组可以让我们用一条正则提取出多个信息,例如:
s = '姓名:张三;性别:男;电话:138123456789'
m = re.search('姓名[::](\w+).*?电话[::](\d{11})', s)
if m:
name = m.group(1)
phone = m.group(2)
print(f'name:{name}, phone:{phone}')
结果:
name:张三, phone:13812345678
示例二:(?P<name>exp)
有时还是会用到的, (?P=name)
则很少情况下会用到。我想了一个(?P=name)
的使用示例,给大家看下效果:
s = '''
<name>张三</name>
<age>30</age>
<phone>138123456789</phone>
'''
pattern = r'<(?P<name>.*?)>(.*?)</(?P=name)>'
It = re.findall(pattern, s)
结果:
[('name', '张三'), ('age', '30'), ('phone', '138123456789')]
2.2、零宽断言
语法 | 描述 |
---|---|
(?=exp) | 匹配exp前面的位置 |
(?<=exp) | 匹配exp后面的位置 |
(?!exp) | 匹配后面跟的不是exp的位置 |
(?<!exp) | 匹配前面不是exp的位置 |
注意
:正则中常用的前项界定 (?<=exp)
和前项否定界定 (?<!exp)
在 Python 中可能会报错:look-behind requires fixed-width pattern,原因是 python 中 前项界定的表达式必须是定长的,看如下示例:
(?<=aaa) # 正确
(?<=aaa|bbb) # 正确
(?<=aaa|bb) # 错误
(?<=\d+) # 错误
(?<=\d{3}) # 正确
2.3、条件匹配
这大概是最复杂的正则表达式了。语法如下:
语法 | 描述 |
---|---|
(?(id/name)yes!no) | 如果指定分组存在,则匹配yes模式,否则匹配no模式 |
此语法极少用到,印象中只用过一次。
(ps:表格内语法错误 不是"!"改为"|")
以下示例的要求是:如果以 _ 开头,则以字母结尾,否则以数字结尾。
s1 = '_abcd'
s2 = 'abc1'
pattern = '(_)?[a-zA-Z]+(?(1)[a-zA-Z]|\d)'
re.search(pattern, s1).group()
re.search(pattern, s2).group()
结果:
_abcd
abc1
2.4、findall
Python 中的 re.findall
是个比较特别的方法。我们看这个方法的官方注释:
Return a list of all non-overlapping matches in the string.
If one or more capturing groups are present in the pattern, return
a list of groups; this will be a list of tuples if the pattern
has more than one group.
Empty matches are included in the result.
简单来说,就是
- 如果没有分组,则返回整条正则匹配结果的列表;
- 如果有 1 个分组,则返回分组匹配到的结果的列表;
- 如果有多个分组,则返回分组匹配到的结果的元组的列表。
看下面的例子:
s = 'aaa123bbb456ccc'
re.findall('[a-z]+\d+', s) # 不包含分组
re.findall('[a-z]+(\d+)', s) # 包含一个分组
re.findall('([a-z]+(\d+))', s) # 包含多个分组
re.findall('(?:[a-z]+(\d+))', s) # ?: 不捕获分组匹配结果
结果:
['aaa123', 'bbb456']
['123', '456']
[('aaa123', '123'), ('bbb456', '456')]
['123', '456']
零宽断言中讲到 Python 中前项界定必须是定长的,这很不方便,但是配合 findall 有分组时只取分组结果的特性,就可以模拟出非定长前项界定的效果了。