大家好呀,昨天我们在一起讨论了匹配规则中的概括字符集、数量词、边界匹配符,今天我们来讨论下组的概念,还有正则中相对复杂的贪婪与非贪婪。
我们为什么要有组这个概念呢,因为一个字符串中,我们有时候只想要其中某一连续的满足某个条件的字符串。
例如content = '发布于2018/12/23',我们需要提取出其中的发布时间,用之间的\d是提取不出来的,因为\d提取不出/,这个时候就可以用到组的概念了。
现在,我们来看看什么是组:
image.png老规矩,还是用代码来解释地好:
# 实现功能:提取出文章发布日期
import re
content = '发布于2018/12/23'
result = re.findall('.*?(\d.*\d)', content)
# 这一行的.*表示匹配除换行符外的任意字符,?表示非贪婪匹配,这个放在后面讲
# (\d.*\d)表示一个组,以数字开头,以数字结尾,.*表示中间可以是除换行以外的任意字符
# 最终返回的结果就是括号内匹配到的结果。
print(result)
# 得到的结果是['2018/12/23']
俗话说,没有对比就没有伤害,上述代码,如果不加括号呢,会是什么结果呢?
# 实现功能:提取发布时间
import re
content = '发布于2018/12/23'
result = re.findall('.*?\d.*\d', content)
print(result)
# 得到的结果是['发布于2018/12/23']
# 因为python默认会在正则表达式首尾各添加一个括号,第三行代码其实等价于
# result = re.findall(('.*?\d.*\d'), content)
当然了,正则表达式中是可以存在多个组的,例如下面这种情况:
# 实现功能:提取发布时间和发布人
import re
content = '发布于2018/12/23,发布人:十月'
result = re.findall('.*?(\d.*\d).*:(.*)', content)
# 这里的前一部分和上面的代码是一样的意思,两个括号之间的内容.*:表示中间是除换行符以外的任意字符,直到遇见:才终止,进入第二个组。
# 所以上述正则表达式的意思是:以除换行符以外的任意字符开头,直到遇见第一个组,以数字开头,以数字结尾,这样就能匹配到发布时间2018/12/23,然后又是除换行符外的任意字符,直到遇见:进入第二个组,分号后面所有的内容构成第二个组,匹配到发布人十月
print(result)
# 得到的结果是[('2018/12/23', '十月')]
会发现得到的结果好像和我们想的不一样呀,我们希望得到 ('2018/12/23', '十月') ,虽然上述结果我们也方便获取我们想要的内容,但是能不能直接获取到类似 ('2018/12/23', '十月') 呢?
这就需要用到re的另一个方法了,match方法。
# 实现功能:提取发布时间和发布人
import re
content = '发布于2018/12/23,发布人:十月。'
result = re.match('.*?(\d.*\d).*:(.*)', content)
# match方法的参数和findall是一样的,返回的结果是SRE_Match对象,对象有几个方法需要了解一下
print(result.group())
# 得到的结果是'发布于2018/12/23,发布人:十月',该方法默认是result.group(0)
# 前文说过re.match('.*?(\d.*\d).*:(.*)', content)等价于
# re.match('(.*?(\d.*\d).*:(.*))', content)
# result.group(0)获取的内容就是最外层的括号匹配的内容。
print(result.group(1))
# 得到的结果是'2018/12/23',获取的内容是(\d.*\d)匹配到的内容
print(result.group(2))
# 得到的结果是'十月',获取的内容是(.*)匹配到的内容
print(result.groups())
# 得到的结果是('2018/12/23', '十月')
在用match方法的时候有一个需要注意的地方,很重要,非常容易导致出错,老规矩,用代码解释:
import re
content = '评论数:12'
result = re.match('\d', content)
print(result)
# 得到的结果是None,如果直接print(result.group())是会报错的。
# 原因在于match方法是从content第一个字符开始去匹配\d,如果未匹配到,直接就返回None。这里因为content第一个字符不是数字,所以直接返回None
到这里,我们的组就讨论完了,接下来我们进入正则的难点,也就是贪婪与非贪婪,老规矩,还是通过代码来解释,因为能更好地看到效果:
先来看下非贪婪模式:
# 实现功能:提取发布时间,比较贪婪与非贪婪
import re
content = '发布于2018/12/23'
result = re.findall('.*?(\d.*\d)', content)
# 这里的?表示的就是非贪婪模式,第一个.*会尽可能少地去匹配内容,因为后面跟的是\d,所以碰见第一个数字就终止了。
print(result)
# 得到的结果是['2018/12/23']
再来看下贪婪模式:
import re
content = '发布于2018/12/23'
result = re.findall('.*(\d.*\d)', content)
# 这里的第一个.*后面没有添加问号,表示的就是贪婪模式,第一个.*会尽可能多地去匹配内容,后面跟的是\d,碰见第一个数字并不一定会终止,当它匹配到2018的2的时候,发现剩下的内容依然满足(\d.*\d),所以会一直匹配下去,直到匹配到12后面的/的时候,发现剩下的23依然满足(\d.*\d),但是如果再匹配下去,匹配到23的2的话,剩下的3就不满足(\d.*\d)了,所以第一个.*就会停止匹配,(\d.*\d)最终匹配到的结果就只剩下23了。
print(result)
# 得到的结果是['23']
我们再来看下这段代码:
import re
content = '发布于2018/12/23'
result = re.findall('.*(\d.*?\d)', content)
print(result)
# 得到的结果是['23'],原因在于第一个.*是贪婪模式,会一直匹配到12后面的/,这样结果就是['23']
再来看下这段代码:
import re
content = '发布于2018/12/23'
result = re.findall('.*?(\d.*?\d)', content)
# 这里的第一个.*?表示非贪婪模式,匹配到2018前面的'于'之后就停止了
# 括号里的.*?也是表示非贪婪模式,括号里的内容从2018的2开始匹配,因为后面一个数字是0,那么也就满足了(\d.*?\d),所以就直接返回结果了,同样的,接下来的18也是这样,一直匹配到23才结束。
print(result)
# 得到的结果是['20', '18', '12', '23']
简单来说,贪婪模式就是尽可能多地去匹配字符,非贪婪模式就是尽可能少地去匹配字符,python默认采取的是贪婪模式。
贪婪与非贪婪在使用的要慎重,因为一不小心就容易出错,匹配到的结果并不是我们想要的。
好了,今天的分享就到这里结束了,大家要动手实践哦,明天我们分享re的另外两个方法sub和search,其中的sub方法的设计理念非常值得我们去学习,我们明天再见。
网友评论