python 正则表达式findall匹配问题

1.今天在做一道匹配邮箱的题目,发现re.findall()匹配的时候如果有括号,只能取到括号里面的内容:

有以下字符串y,使用正则表达式匹配出字符串中的邮箱地址。
y= '123@qq.comaaa@163.combbb@126.comasdfasfs33333@adfcom'

要是想匹配邮箱地址的话,必须做一些其他的工作,下面提供了三种写法:

import re
y= '123@qq.comaaa@163.combbb@126.comasdfasfs33333@adfcom'
#第一种写法:
ret1=re.findall('[0-9a-z]+@+[0-9a-z]+\.com',y)
print(ret1)
##第二种写法:
ret2=re.findall('(\w+@(qq|126|163)\.com)',y)
for i in ret2:
    print(i[0])
#第三种写法:
ret3=re.findall('\w+@(?:qq|126|163)\.com',y)
print(ret3)


2.对于以上的问题我找了一些关于re.findall() 的资料,看看有没有一些合理的解释:

(1)官方文档

(2)发现看了官方文档有些地方还是不够理解

Python的re模块中match、search、findall、finditer的区别

(3)或许菜鸟教程的这个解释的更直白

3.在一篇博文里(python re 模块 findall 函数用法简述)找到了一个很好说明问题的例子,摘录在下面了,希望加深“re模块下的findall()函数 ,带括号与不带括号的区别”的理解。

>>> import re  
>>> s = "adfad asdfasdf asdfas asdfawef asd adsfas " 
 
>>> reObj1 = re.compile('((\w+)\s+\w+)')  
>>> reObj1.findall(s)  
[('adfad asdfasdf', 'adfad'), ('asdfas asdfawef', 'asdfas'), ('asd adsfas', 'asd')]  
 
>>> reObj2 = re.compile('(\w+)\s+\w+')  
>>> reObj2.findall(s)  
['adfad', 'asdfas', 'asd']  
 
>>> reObj3 = re.compile('\w+\s+\w+')  
>>> reObj3.findall(s)  
['adfad asdfasdf', 'asdfas asdfawef', 'asd adsfas']  


按以上代码例子讲解:
findall函数返回的总是正则表达式在字符串中所有匹配结果的列表,此处主要讨论列表中“结果”的展现方式,即findall中返回列表中每个元素包含的信息。

1.当给出的正则表达式中带有多个括号时,列表的元素为多个字符串组成的tuple,tuple中字符串个数与括号对数相同,字符串内容与每个括号内的正则表达式相对应,并且排放顺序是按括号出现的顺序。
2.当给出的正则表达式中带有一个括号时,列表的元素为字符串,此字符串的内容与括号中的正则表达式相对应(不是整个正则表达式的匹配内容)。
3.当给出的正则表达式中不带括号时,列表的元素为字符串,此字符串为整个正则表达式匹配的内容。

4.随着研究的深入,发现关于这个问题,已经知乎也有人研究过,而且问题没有我想的那么简单。

在博文里((Python的正则表达式中的圆括号到底如何使用?))里面有个具体的例子来看一下吧:

>>> a = "one two three four five six"
>>> b = re.findall("(one)|(two)|(three)|(four)|(five)|(six)",a)#加上了"|"号
>>> print(b)
[('one', '', '', '', '', ''), ('', 'two', '', '', '', ''),
('', '', 'three', '', '', ''),('', '', '', 'four', '', ''),
('', '', '', '', 'five', ''),('', '', '', '', '', 'six')]
1这个结果是因为按组匹配所以有元组,每个元组都有六个元素。
2而因为是条件匹配,列了六种匹配条件,于是findall匹配六次,结果列表有六个元素。
3又因为每次匹配只能用一种条件,所以按组匹配结果列表中的每个元组只有一组有值而其他均为空。

再在跟官方文档对应一下:

Empty matches are includedinthe result.

通过例子,理解起来这句话会容易很多吧。

后来又看到一个例子,发现对大括号里面的{m,},{m,n},{m}这类问题也有盲点,就一并研究了一下:

>>> x = "3 min 46 sec 300 ms"
#分秒毫秒的提取
>>> print(re.findall(r"(\d{0,}) (min|sec|ms)",x))
[('3', 'min'), ('46', 'sec'), ('300', 'ms')] #会不会很方便?

有的时候发现这个表达式很简单,但真正的去做匹配的时候你会发现,好多东西掌握的不够好。有些规则会随着python的不同,结果也会不同,遇到问题的时要多方求证,一方面查看官方文档,另一方面也要找例子来验证,总结规律。

Note
Due to the limitation of the current implementation the character following an empty match is not included in a next match, so findall(r'^|\w+', 'two words') returns ['', 'wo', 'words'] (note missed “t”). This is changed in Python 3.7.
#python3.7中的结果
import re
ret=re.findall(r'^|\w+', 'two words')
print(ret)

想了解的更深的话,这篇文章或许也有帮助:

正则表达式零宽断言详解(?=,?<=,?!,?<!) - 无所事事者爱嘲笑 - 博客园

牛刀小试一下吧:

1.把以下文本中的单词全部提取出来

str="hello,world,ha ha"
re.findall('\w+',"hello,workd,ha ha")
re.split(' |,',"hello,workd,ha 



2. 匹配用一个空格分隔的任意一对单词,

比如字符串'huang feihong fang shiyu' ,取出的应该是huang feihong 和fang shiyu两个姓名

 import re
 mystr = 'huang feihong fang shiyu'
 print(re.findall('(\w+ \w+)',mystr))

编辑于 2018-06-10

文章被以下专栏收录