正则表达式(二)——Python中的相关方法
正则函数
match、search、findall、finditer、split、sub
返回一个对象:match、search、finditer
返回一个列表:findall、split
其中match、search与findall都有两个匹配方式:简单匹配和分组匹配
1、match
格式
match(正则表达式,字符串,标志位)
match( r'xxx' , str , flags = 0 )
一般只用到了前两个参数
match( r'XXX' , str )
flags(标志位)用于修改正则表达式的匹配方式,常见的工作方式:
re.I 使匹配对大小写不敏感
re.L 做本地化识别(local-aware)匹配
re.M 多行匹配,影响^和$,不匹配换行符
re.S 使.匹配包含换行在内的所有字符(可能是改变元字符 . 的用法?)
……
匹配结果
简单匹配下,匹配成功返回一个Match对象,失败则返回None。
Match对象的group()返回匹配到的结果字符串(与正则表达式字符串完全对应的字符串)
常用的判断方法是:
import re if re.match(正则表达式 , 测试字符串): print('OK!') else: print('Failed!')
如果match的第一个参数是一个没有加任何正则表达式的规则的字符串,则默认匹配从测试字符串的开头匹配参数一中的字符串,匹配到了就返回一个Match对象,该Match对象的group()即为参数一中的字符串,group()和group(0)的效果相同。匹配不到就返回None。
例子
#单纯字符串匹配,只从开头匹配 #result1从开头能匹配上 result1=re.match('abc','abcdef') #result2从开头匹配不上 result2=re.match('abc','1abc') result1 <re.Match object; span=(0, 3), match='abc'> result1.group() 'abc' print(result2) None
分组匹配下:
m=re.match(带括号的正则表达式 , 字符串)
通过group和groups方法提取分组结果
举个例子说明如下:
import re m=re.match(r'^(\d{3})-(\d{3,8})$','010-12345')
m.group(0)
'010-12345' m.group(1) '010' m.group(2) '12345' m.groups() ('010', '12345')
group(0)是原始字符串,group(1)、group(2)、……表示第1、2、……个子串。此外groups()是一个包含了所有子串的tuple。
补充一个冷门知识点:
正则表达式中的?P<A1>格式:用于groupdict()方法以及之后模块6的替换sub()方法中
直接上例子:
str='hello world' re1=re.compile(r'(?P<n1>h)(?P<n2>\w+)') ret=re1.match(str) ret.groupdict() {'n1': 'h', 'n2': 'ello'}
所以正则式中的?P<n2>\w+的意思就是声明了一个dict,其中一个Key-Value对为:Key为n2,Value为\w+匹配的结果。
通过groupdict()来查看这个dict。
2、search
re.search()方法扫描整个字符串,并返回第一个成功的匹配。如果匹配失败,则返回None。
与re.match()方法的区别:re.match()要求必须从字符串的开头进行匹配,如果字符串的开头不匹配,整个匹配就失效了。
re.search()并不要求从字符串的开头进行匹配,也就是说,正则表达式可以是字符串的一部分(与之区别的是match就要求正则表达式必须从头到尾完全与字符串匹配,否则就是不匹配)
格式与match类似:
re.search(正则表达式,字符串,标志位)
re.search(r'',str,flags=0)
例一:提取'Hello 123456789 Word_This is just a test 666 Test'中的数字
str='Hello 123456789 Word_This is just a test 666 Test' re6=r'(\d+).*?(\d+)' #必须是非贪婪匹配*? 否则后一个()只会接收最后一个数字 ret=re.search(re6,str) if ret: for i in ret.groups(): print (i) else: print(ret)
结果:
123456789 666
例二:上例的字符串,只用(\d+)去匹配
str='Hello 123456789 Word_This is just a test 666 Test' re7=r'(\d+)' ret=re.search(re7,str) if ret: for i in ret.groups(): print (i) else: print(ret) #结果 123456789
重要!!!!!!!
正则串中有多少个括号(),匹配结果的groups中就有几个结果。另外,由于search只取第一个成功匹配的结果,所以即使后边还有可以匹配成功的字符串,也不计入groups中
利用正则,查找字符串中第一个中文字符的起始位置(此时不用find):
import re pattern=re.compile(r'[^\u0000-\u007f]') ret=pattern.serch(fn)
index=ret.start()#查找到的匹配项在原str中的位置
3、findall
search只会返回第一个成功匹配的字符串,而findall会将所有匹配到的字符串,都放置在同一个List中。
①优先匹配原则
findall的正则式中的括号是优先匹配和分组匹配的标志,一个括号对应一个组。
findall与search、match等不同的是,它会优先取组里的内容,但是可以使用?:来关闭。
str='123 www.bing.com123www.google.com123' re81=r'www.(bing|google).com' re82=r'www.(?:bing|google).com' ret1=re.findall(re81,str) ret2=re.findall(re82,str) print(ret1,'\n',ret2) ['bing', 'google'] ['www.bing.com', 'www.google.com']
当正则式为re81——www.(bing|google).com时,会只匹配括号中的内容,即bing|google,相当于正则式就是括号中的内容(★★★★),即 re81=r'bing|google'
如果想取消优先匹配,只需在(后边加上?:即可。
由于括号常与或|关联,所以在正则式中用到或(A|B)的格式时,一定要注意关闭优先匹配?:,然后再匹配,这样就能发挥或的作用了,就像上边的正则式re82。
这一点的总结:
a、当正则没有分组就是返回整个正则式的匹配
b、当正则式中有一个分组就是这个分组的匹配
c、多个分组的匹配方式与一个分组相同,但结果会将分组装到tuple中返回
关于c的举例:
content= 'email:12345678@163.com,...' \ 'email:2345678@163.com,...' \ 'email:345678@163.com' re10=re.compile(r'(\d+)@(\w+.com)') ret_findall=re10.findall(content) print(ret_findall) [('12345678', '163.com'), ('2345678', '163.com'), ('345678', '163.com')]
因此当我们同时需要整个正则和各个分组的匹配,我们需要为整个正则式加括号。
re10=re.compile(r'((\d+)@(\w+.com))') [('12345678@163.com', '12345678', '163.com'), ('2345678@163.com', '2345678', '163.com'), ('345678@163.com', '345678', '163.com')]
对整个正则式的匹配结果,是返回tuple的第一个元素。
②匹配顺序
一旦有字符被匹配到,就会把匹配到的字符拿走,然后再匹配剩下的字符,如下:
#匹配顺序 str='a2b3c4d5' re8=r'\d+\w\d+' ret=re.findall(re8,str) print(ret) ['2b3', '4d5']
③匹配空值
当匹配规则为空''时,如果没有匹配到也会把空值放到结果中:
re8=r'' str='abcdef' ret=re.findall(re8,str) print(ret) ['', '', '', '', '', '', '']
所以写Python正则时,要尽量使正则不为空,所以正则式为一个单字符+元字符的形式时,尽量用+而不是*符(因为*表示{0,}——任意个字符)
下面给出一个推荐和不推荐的写法的例子
str='alex' ret1=re.findall(r'\w+',str) #推荐用 + ret2=re.findall(r'\w*',str) #不推荐用 * print(ret1,'\n',ret2) ['alex'] ['alex', '']
当然这里的推荐与不推荐,仅仅针对findall,无关match和search
④贪婪与非贪婪
这点与match、search倒是类似,非贪婪匹配也是在+后写个?,构成+?
例如:
ret1=re.findall(r'\d+','a23b') ret2=re.findall(r'\d+?','a23b') ret3=re.findall(r'a(\d+?)b','a23b') print(ret1,'\n',ret2,'\n',ret3) ['23'] ['2', '3'] ['23']
值得注意的是第三种情况,当括号()位于a,b之间的时候?失效,整个匹配又变成了和第一种情况一样的贪婪匹配。
最后对于findall的总结:
1、findall会查找所有匹配结果,放在最后的List中
2、有字符串匹配到后,就会在原串中把匹配到的子串删去,再从剩下的串中匹配。
3、findall的()代表优先匹配,而不是分组匹配,更何况,findall本身就不支持分组匹配。
findall正则式里有括号,可以把该正则式只看做括号中的内容(详见①优先匹配)
4、非贪婪匹配如果夹在两个字符之间,就会失效变为贪婪匹配(详见④贪婪匹配)。
4、finditer
返回一个可迭代的MatchObject类型的Iterator:
p=re.compile(r'\d+') str='12 dsnjfkbsfj1334jnkb3kj242' w1=p.finditer(str) w2=p.findall(str) print(w1) print(w2) <callable_iterator object at 0x000002553AD77408> ['12', '1334', '3', '242']
访问w1的方法是通过for循环迭代器实现:
for ret in w1: print(ret.group(),ret.span()) 12 (0, 2) 1334 (14, 18) 3 (22, 23) 242 (25, 28)
MatchIterator对象的方法:
group()方法可以得到匹配到的结果;在有分组的情况下group(n)表示第n个分组的匹配结果
span()方法得到一个tuple,其中存储匹配结果在原串中的(首下标,尾下标+1)。
findall与finditer
学习自:https://blog.csdn.net/naipeng/article/details/94744621
相同点:
二者都可以获取所有匹配结果
不同点:
findall返回一个list;finditer返回一个MatchObject类型的Iterator
这也就决定了:
提取finditer的信息,要通过for循环,并调用每个对象的group()方法;提取findall的信息可以直接print,或是其他list操作方法
content= 'email:12345678@163.com, email:2345678@163.com, email:345678@163.com '
需求1:提取所有邮箱信息,不需要分组
re10=re.compile('\d+@\w+.com') ret1_finditer=re10.finditer(content) for i in ret1_finditer: print(i.group()) ret2_findall=re10.findall(content) print(ret2_findall)
12345678@163.com 2345678@163.com 345678@163.com ['12345678@163.com', '2345678@163.com', '345678@163.com']
需求2:提取出所有电话号码和邮箱类型
re10=re.compile(r'(\d+)@(\w+.com)') ret_finditer=re10.finditer(content) ret_findall=re10.findall(content) for i in ret_finditer: print(i.group(1),' ',i.group(2)) print(ret_findall)
12345678 163.com 2345678 163.com 345678 163.com [('12345678', '163.com'), ('2345678', '163.com'), ('345678', '163.com')]
可见,当有分组时,finditer通过group(n)访问第n个分组的匹配结果;而findall则是把每个组查找的结果综合为一个tuple。
5、分割——split
格式:
re.split(正则式,待分割字符串,maxsplit=0 , flags=0)
参数maxsplit——最大分割次数
返回一个list
①优先匹配原则
由于split和findall返回的都是所有元素的列表,因此他和findall一样具有优先匹配的特性。
②有组与无组
有组时,结果会包含处于分割位置的字符串,即分成[ 分割位置前 , 分隔位置处 , 分割位置后]
无组时,结果不含处于分割位置的字符串,即分成[ 分割位置前 , 分割位置后 ]
origin = 'hello alex bcd abcd lge acd 19' #无组 re1=re.compile(r'a\w+') #有组 re2=re.compile(r'(a\w+)')#组中包含a re3=re.compile(r'a(\w+)')#组中不含a print(re1.split(origin)) print(re2.split(origin)) print(re3.split(origin))
结果:
['hello ', ' bcd ', ' lge ', ' 19'] ['hello ', 'alex', ' bcd ', 'abcd', ' lge ', 'acd', ' 19'] ['hello ', 'lex', ' bcd ', 'bcd', ' lge ', 'cd', ' 19']
连续n个匹配串
如果分割位置在开头或者最后,则分割后的第一个分割串之前或者最后一个分割串之后会多一个空串;
如果开头或者结尾,有n个匹配串,就会在分割后的list中的开头或结尾多n个空串。
而在中间的连续n个匹配串,则会多n-1个空串(当只有一个匹配串时,没有空串),这一点也适用于[](见下文例子中的origin4和re2的组合)
origin1='aabaadecacaa'#首尾2个匹配串 origin2='abaadecaca' #首尾1个匹配串 中间连续2个匹配串 origin3='caaac' #中间连续3个匹配串 origin4='cabac' #用[]间接造成中间连续3个匹配串 re1=re.compile(r'a') re2=re.compile(r'[ab]') print(re1.split(origin1)) print(re1.split(origin2)) print(re1.split(origin3)) print(re2.split(origin4)) ['', '', 'b', '', 'dec', 'c', '', ''] ['', 'b', '', 'dec', 'c', ''] ['c', '', '', 'c'] ['c', '', '', 'c']
消除串中所有异常字符
如果要消去一个字符串中的所有异常字符呢?用[]把全部异常字符包括就可以了。
比如str='a,b ; c . e f ,f g'
异常字符就包括:, ; . 空格
则我们的正则表达式就可以写为:r'[,;\.\s]+'
re1=re.compile(r'[,;\.\s]+') re1.split(str) ['a', 'b', 'c', 'e', 'f', 'f', 'g']
6、替换——sub
格式:re.sub(被替换子串正则式,替换为的子串,str[ , count=0])
count为替换的次数,默认为0全部替换
返回替换后str
str='I get A,I got B,I gut C' substr='hava' re12=re.compile('g.t') ret=[] ret.append(re12.sub(substr,str)) ret.append(re12.sub(substr,str,count=0)) ret.append(re12.sub(substr,str,count=2)) ret.append(re12.sub(substr,str,3)) ret.append(re12.sub(substr,str,2)) for i in range(len(ret)): print(ret[i])
I hava A,I hava B,I hava C
I hava A,I hava B,I hava C
I hava A,I hava B,I gut C
I hava A,I hava B,I hava C
I hava A,I hava B,I gut C
subn:调用格式与sub相同,只是最后返回结果中会统计替换次数。
temp=re.subn('g.t',substr,str) print(temp) ('I hava A,I hava B,I hava C', 3)
其他
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性