正则表达式
本文主要讲的是正则表达式在python中的简单使用,只介绍关键的几个方法函数,就不赘述正则表达式的基础知识了。在我个人的工作学习生活场景中,正则表达式主要用在清洗数据、路由代码、判断用户输入字段是否合规等场景,很简单,拆开来说,无非就下面这些常见的使用场景,使用的函数也就那几个,其他现学现卖就好了。
-
从头到尾只匹配一次,即匹配到第一次就返回结果,若匹配不到就返回None
-
从头开始匹配: re.match()
,例如检验用户输入的手机号是否合规、路由匹配 -
不从头开始匹配: re.search()
,多用在寻找目标字段,可通过其groups属性提取 -
从头到尾找多次
-
re.findall()
,返回匹配到的字符串列表,这个后面细说 -
替换数据
-
re.sub()
,很多时候要清洗文本中的换行符等字符,或者是替换日期格式等操作
从我的角度来说,re.search()
是使用最多的,结合分组、命名等使用方式写个匹配规则,然后search一下,提取目标字段。然后就是标识符需要好好看看,很多时候感觉表达式对了,但是就是匹配不到,很可能是因为标识符没设置,在你掌握了re.search()
和flags
后,已经能应付很多需求了,其他函数也自然就触类旁通,接下来开始正文。
re.match()和re.search()
SRE_Match object
为什么要把re.match()和re.search()放在一起说呢,因为它们两个返回的是同一种对象,官方描述是SRE_Match object
,其打印结果如下所示。
text = '<p>asdasdasaaansdghaasdasdsdfs</p>kkkkkkkkkkkkk'
reg = r'<.*?>(?P<content>.*?)</.*?>'
res01 = re.match(pattern=reg, string=text, flags=re.S + re.M)
res02 = re.search(pattern=reg, string=text, flags=re.S + re.M)
print(res01)
print(res02)
print(type(res01))
print(type(res02))
# 打印结果
<_sre.SRE_Match object; span=(0, 34), match='<p>asdasdasaaansdghaasdasdsdfs</p>'>
<_sre.SRE_Match object; span=(0, 34), match='<p>asdasdasaaansdghaasdasdsdfs</p>'>
<class '_sre.SRE_Match'>
<class '_sre.SRE_Match'>
这个search_match_obj中有几个常用的属性和方法
res = re.search(pattern=reg, string=text, flags=re.S)
res.group() # 只获取第一组的字符串
res.groups() # 获取组元组
res.groupdict() # 获取组的key:value对
res.span() # 匹配的串的开始:结束位 (0, 34)
re.match()
re.match()尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。
函数语法:
re.match(pattern, string, flags=0)
函数参数说明:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式。 |
string | 要匹配的字符串。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 – 可选标志 |
匹配成功re.match()返回一个匹配的对象,否则返回None。可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。
使用场景:例如检验用户输入的手机号是否合规、路由匹配等。
使用示例:
text = 'aa<p>asdasdasaaansdghaasdasdsdfs</p>kkkkkkkkkkkkk'
reg = r'<.*?>(?P<content>.*?)</.*?>'
res = re.match(pattern=reg, string=text, flags=re.S + re.M)
print(res) # 因为一开头就匹配不上,将返回None
# 如下情况则能匹配到
text = 'aa<p>asdasdasaaansdghaasdasdsdfs</p>kkkkkkkkkkkkk'
reg = r'<.*?>(?P<content>.*?)</.*?>'
res = re.match(pattern=reg, string=text, flags=re.S + re.M)
print(res) # SRE_Match object
print(res.group())
print(res.groups())
print(res.groupdict())
打印结果如下:
<_sre.SRE_Match object; span=(0, 34), match='<p>asdasdasaaansdghaasdasdsdfs</p>'>
<p>asdasdasaaa
sdghaasdasdsdfs</p>
('asdasdasaaansdghaasdasdsdfs', 'p')
{'content': 'asdasdasaaansdghaasdasdsdfs'}
(0, 34)
re.search()(重点)
re.search 扫描整个字符串并返回第一个成功的匹配。
函数语法:
re.search(pattern, string, flags=0)
函数参数说明:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。 |
匹配成功re.search()返回一个匹配的对象,否则返回None。可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。
使用场景:例如检验用户输入的手机号是否合规、路由匹配等。
使用示例:多用在寻找目标字段,提取、清洗数据。
import re
text = '<p>asdasdasaaansdghaasdasdsdfs</p>kkkkkkkkkkkkk'
# 一个分组,并命名为content,.*?是非贪婪模式下匹配任意字段
reg = r'<.*?>(?P<content>.*?)</.*?>'
res = re.search(pattern=reg, string=text, flags=re.S + re.M)
print(res)
print(res.group())
print(res.groups())
print(res.groupdict())
# 打印结果如下
<_sre.SRE_Match object; span=(0, 34), match='<p>asdasdasaaansdghaasdasdsdfs</p>'>
<p>asdasdasaaa
sdghaasdasdsdfs</p>
('asdasdasaaansdghaasdasdsdfs',)
{'content': 'asdasdasaaansdghaasdasdsdfs'}
分组的意义,就是在匹配成功的字符串中,再提取()里面的字符串,re.search中通过groupdict()
拿到分组对应的内容。
re.flags
前文中,关于flags的使用如下:
res = re.search(pattern=reg, string=text, flags=re.S + re.M)
re.S + re.M
代表同时使用两种模式,re.S主要是为了让.匹配到n,而re.M则是为了避免因为有n而导致的换行匹配不成功,下面将对各个标识符的模式简要介绍一下,需要了解具体说明的看客还请移步官网等教程。
-
re.I(re.IGNORECASE)
-
忽略大小写匹配 -
re.A(re.ASCII)
-
让 w
,W
,b
,B
,d
,D
,s
和S
只匹配ASCII,而不是Unicode。在默认匹配模式下w+
匹配到了所有字符串,而在ASCII模式下,只匹配到了a、b、c(ASCII编码支持的字符)。 -
只对字符串匹配模式有效,对字节匹配模式无效。 -
re.S(re.DOTALL)
-
DOT表示 .
,ALL表示所有,连起来就是.
匹配所有,包括换行符n
。 -
默认模式下 .
是不能匹配行符n
的。 -
re.M(re.MULTILINE)
-
多行模式,当某字符串中有换行符
n
,默认模式下是不支持换行符特性的,比如:行开头 和 行结尾,而多行模式下是支持匹配行开头的。 -
正则表达式中 ^
表示匹配行的开头,默认模式下它只能匹配字符串的开头;而在多行模式下,它还可以匹配 换行符n
后面的字符。 -
正则语法中
^
匹配行开头、A
匹配字符串开头,单行模式下它两效果一致,多行模式下A
不能识别n
。 -
re.X( re.VERBOSE)
-
详细模式,可以在正则表达式中加注解 -
默认模式下并不能识别正则表达式中的注释,而详细模式是可以识别的。 -
re.L(re.LOCALE)
-
由当前语言区域决定 w
,W
,b
,B
和大小写敏感匹配,这个标记只能对byte样式有效。 -
官方不推荐使用 -
re.U (re.UNICODE)
-
匹配unicode编码支持的字符 -
Python 3 默认字符串已经是Unicode,不推荐使用 -
re.DEBUG
-
显示编译时的debug信息,了解即可 -
re.T(re.TEMPLATE)
-
disable backtracking(禁用回溯),了解即可
小结
标志位总共有9个常量,其中:
-
常用:IGNORECASE、ASCII、DOTALL、MULTILINE、VERBOSE -
几乎不用:LOCALE、UNICODE、TEMPLATE、DEBUG
re.sub()
该函数用于将匹配到字符串替换成目标字符。
语法:
re.sub(pattern, repl, string, count=0, flags=0)
参数:
-
pattern : 正则中的模式字符串。 -
repl : 替换的字符串,也可为一个函数。 -
string : 要被查找替换的原始字符串。 -
count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
示例:
text = 'aa<p>asdasdasaaansdghaasdasdsdfs</p>kkkkkkkkkkkkk'
reg = r'<.*?>'
res = re.sub(pattern=reg, string=text, flags=re.S + re.M, repl=' ')
print(res)
# 打印结果
aa asdasdasaaa
sdghaasdasdsdfs kkkkkkkkkkkkk
re.subn(pattern, repl, string, count=0, flags=0) 函数与 re.sub函数 功能一致,只不过返回一个元组 (字符串, 替换次数)。
re.split()
split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:
re.split(pattern, string, maxsplit=0, flags=0)
参数:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串 |
maxsplit | 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 – 可选标志 |
示例
text = 'aa<p>asdasdasaaansdghaasdasdsdfs</p>kkkkkkkkkkkkk'
reg = r'<.*?>'
res = re.split(pattern=reg, string=text, flags=re.S + re.M)
print(res) # 因为一开头就匹配不上,将返回None
re.findall()
在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。
注意: match 和 search 是匹配一次 findall 匹配所有。
语法格式为:
re.findall(pattern, string, flags=0)
参数:
-
string : 待匹配的字符串。 -
pos : 可选参数,指定字符串的起始位置,默认为 0。 -
endpos : 可选参数,指定字符串的结束位置,默认为字符串的长度。
使用场景:多用于找到所有目标字段。
实例
str = 'aabbabaabbaa'
# 一个"."就是匹配除 n (换行符)以外的任意一个字符
print(re.findall(r'a.b',str)) # ['aab', 'aab']
# *前面的字符出现0次或以上
print(re.findall(r'a*b',str)) # ['aab', 'b', 'ab', 'aab', 'b']
# 贪婪,匹配从.*前面为开始到后面为结束的所有内容
print(re.findall(r'a.*b',str)) # ['aabbabaabb']
# 非贪婪,遇到开始和结束就进行截取,因此截取多次符合的结果,中间没有字符也会被截取
print(re.findall(r'a.*?b',str)) # ['aab', 'ab', 'aab']
# 非贪婪,与上面一样,只是与上面的相比多了一个括号,只保留括号的内容
print(re.findall(r'a(.*?)b',str)) # ['a', '', 'a']
str = '''aabbab
aabbaa
bb''' # 后面多加了2个b
# 没有把最后一个换行的aab算进来
print(re.findall(r'a.*?b',str)) # ['aab', 'ab', 'aab']
# re.S不会对n进行中断
print(re.findall(r'a.*?b',str,re.S))
# ['aab', 'ab', 'aab', 'aan b']
re.finditer(pattern, string, flags=0
用法与re.findall()
差不多,但是前者返回的是迭代器,需要对其进行遍历,才能获取数据,后者则直接是个列表,为什么这么设计呢?这里我本人暂时也不清楚,貌似是因为使用finditer时,无需手动将整个正则用()括起来group()代表整个正则的匹配,这里的需求场景比较特殊,后面若深挖,我将继续分享。
re.fullmatch()
re.fullmatch()的返回值re.match()和re.search()一样都是一个SRE_Match object,fullmatch是完全匹配(从字符串开头到结尾),而match()只是从头匹配,但从理论上来说,能用fullmatch实现的,用match也都能实现。
即,fullmatch(pattern, string, flags=0)
,是match函数的完全匹配(从字符串开头到结尾)版本。
示例:
qq = '1234567890'
res01 = re.fullmatch(r'[1-9]d{4,11}', qq)
res02 = re.match(r'^[1-9]d{4,11}$', qq)
print(res01)
print(res02)
# <_sre.SRE_Match object; span=(0, 10), match='1234567890'>
# <_sre.SRE_Match object; span=(0, 10), match='1234567890'>
re.compile()
在Python中使用正则表达式时,re模块内部会干两件事情:
-
编译正则表达式,如果正则表达式的字符串本身不合法,会报错;
-
用编译后的正则表达式去匹配字符串。
那么如果一个正则表达式要重复使用几千次,出于效率的考虑,应该先把这个正则先预编译好,接下来重复使用时就不再需要编译这个步骤了,直接匹配,提高我们的效率。
re.compile(pattern, flags=0)
接受pattern和flags两个参数,使用简单示例如下:
re_telephone = re.compile(r'^(d{3})-(d{3,8})$') # 编译
A = re_telephone.match('010-12345').groups() # 使用
print(A) # 结果 ('010', '12345')
B = re_telephone.match('010-8086').groups() # 使用
print(B) # 结果 ('010', '8086')
小结
方法分类
查找一个匹配项
-
re.search()
-
re.match()
-
re.fullmatch()
查找多个匹配项
-
re.findall()
-
re.finditer()
替换
-
re.sub() -
re.subn()
分割
-
re.split()
编译
-
re.compile()
注意细节
-
字节串 与 字符串
模式和被搜索的字符串既可以是 Unicode 字符串 (str) ,也可以是8位字节串 (bytes)。但是,Unicode 字符串与8位字节串不能混用。
-
反斜杠和
r
的使用正则表达式使用反斜杠
/
来表示特殊形式,或者把特殊字符转义成普通字符。反斜杠在普通的 Python 字符串里也有相同的作用,所以就产生了冲突。解决办法是对于正则表达式样式使用 Python 的原始字符串表示法;在带有 ‘r’ 前缀的字符串字面值中,反斜杠不必做任何特殊处理。 -
重复使用某个正则
如果要重复使用某个正则表达式,可使用
re.compile()
优化。
正则表达式网站
-
练习网站:https://regexone.com/
由浅入深,旁边还有提示,刷完后正则表达式的掌握程度就立马上来了!
-
测试网站:https://regexr.com/
把目标文本粘贴进去,然后在上方输入你的表达式,匹配到的都会高亮显示,且提示非常全,下面还会有匹配表达式的规则解析说明。
参考资料
https://zhuanlan.zhihu.com/p/127807805
https://www.runoob.com/python/python-reg-expressions.html
https://regexr.com/
https://regexone.com/
原文始发于微信公众号(豆子前端):python正则表达式必知必会的基础
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/57040.html