python正则表达式系列(5)——零宽断⾔
本⽂主要总结了python正则零宽断⾔(zero-length-assertion)的⼀些常⽤⽤法。
1. 什么是零宽断⾔
有时候在使⽤正则表达式做匹配的时候,我们希望匹配⼀个字符串,这个字符串的前⾯或后⾯需要是特定的内容,但我们⼜不想要前⾯或后⾯的这个特定的内容,这时候就需要零宽断⾔的帮助了。所谓零宽断⾔,简单来说就是匹配⼀个位置,这个位置满⾜某个正则,但是不纳⼊匹配结果的,所以叫“零宽”,⽽且这个位置的前⾯或后⾯需要满⾜某种正则。
⽐如对于⼀个字符串:”finished going done doing”,我们希望匹配出其中的以ing结尾的单词,就可以使⽤零宽断⾔:
import re
s = 'finished going done doing'
p = repile(r'\b\w+(?=ing\b)')
print'【Output】'
print [x + 'ing'for x in re.findall(p,s)]
【Output】
['going', 'doing']
可以看出从中匹配出了’going’和’doing‘两个单词,达到⽬的。
这⾥正则中使⽤的(?=ing\b)就是⼀种零宽断⾔,它匹配这样⼀个位置:这个位置有⼀个’ing’字符串,后⾯跟着⼀个’\b’符号,并且这个位置前⾯的字符串满⾜正则:\b\w+,于是匹配结果就是:['go','do']
2. 不同的零宽断⾔
零宽断⾔分为四种:正预测先⾏断⾔、正回顾后发断⾔、负预测先⾏断⾔、负回顾后发断⾔,不同的断⾔匹配的位置不同。
总结⼀下,这⼏个仿佛说的不是”⼈话”的令⼈费解的名词可以这样理解:其中的“正”指的是肯定预测,即某个位置满⾜某个正则,⽽与之对应的“负”则指的是否定预测,即某个位置不要满⾜某个正则;其中的“预测先⾏”则指的是“往后看”,“先往后⾛”的意思,即这个位置是出现在某⼀个字符串后⾯的,⽽与之相反的“回顾后发”则指的是相反的意思:“往前看”,即匹配的这个位置是出现在某个字符串的前⾯的。
不理解没关系,我们⽤实例说话,下⾯对每种零宽断⾔进⾏详细介绍。
1. 正预测先⾏断⾔:(?=exp)
匹配⼀个位置(但结果不包含此位置)之前的⽂本内容,这个位置满⾜正则exp,举例:匹配出字符串s中以ing结尾的单词的前半部分:
s = "I'm singing while you're dancing."
p = repile(r'\b\w+(?=ing\b)')
print'【Output】'
print re.findall(p,s)
【Output】
['sing', 'danc']
2. 正回顾后发断⾔:(?<=exp)
匹配⼀个位置(但结果不包含此位置)之后的⽂本,这个位置满⾜正则exp,举例:匹配出字符串s中以do开头的单词的后半部分:
s = "doing done do todo"
p = repile(r'(?<=\bdo)\w+\b')
print'【Output】'
print re.findall(p,s)
【Output】
['ing', 'ne']
3. 负预测先⾏断⾔:(?!exp)
匹配⼀个位置(但结果不包含此位置)之前的⽂本,此位置不能满⾜正则exp,举例:匹配出字符串s中不以ing结尾的单词的前半部分:
s = 'done run going'
p = repile(r'\b\w+(?!ing\b)')
print'【Output】'
print re.findall(p,s)
【Output】
['done', 'run', 'going']
可见,出问题了,这不是我们预期的结果(预期的结果是:done和run),这是因为负向断⾔不⽀持匹配不定长的表达式,将p改⼀下再匹配:
python正则表达式不包含s = 'done run going'
p = repile(r'\b\w{2}(?!ing\b)')
print'【Output】'
print re.findall(p,s)
【Output】
['do', 'ru']
可见⼀次只能匹配出固定长度的不以ing结尾的单词,没有完全达到预期。这个问题还有待解决。
4. 负回顾后发断⾔:(?<!exp)
匹配⼀个位置(但结果不包含此位置)之后的⽂本,这个位置不能满⾜正则exp,举例:匹配字符串s中不以do开头的单词:
s = 'done run going'
p = repile(r'(?<!\bdo)\w+\b')
print'【Output】'
print re.findall(p,s)
【Output】
['done', 'run', 'going']
可见也存在与负预测先⾏断⾔相同的问题,改⼀下:
s = 'done run going'
p = repile(r'(?<!\bdo)\w{2}\b')
print'【Output】'
print re.findall(p,s)
【Output】
['un', 'ng']
5. 正向零宽断⾔的结合使⽤
举例:字符串ip是⼀个ip地址,现在要匹配出其中的四个整数:
ip = '160.158.0.77'
p = repile(r'(?<=\.)?\d+(?=\.)?')
print'【Output】'
print re.findall(p,ip)
【Output】
['160', '158', '0', '77']
6. 负向零宽断⾔的结合使⽤
举例:匹配字符串s中的⼀些单词,这些单词不以’x’开头且不以’y’结尾:
s = 'xaay xbbc accd'
p = repile(r'(?<!\bx)\w+(?!y\b)')
print'【Output】'
print re.findall(p,s)
【Output】
['xaay', 'xbbc', 'accd']
可见这⾥因为负向断⾔不⽀持不定长表达式,所以也存在和前⾯相同的问题。
3. 零宽断⾔的应⽤
1. 匹配html标签之间的内容
s = '<span>Hello world!</span>'
p = repile(r'(?<=<(?:\w+)>(.*)(?=</\1>))')
print'【Output】'
print re.findall(p,s)
# 报错:error: look-behind requires fixed-width pattern
上⾯的报错是因为零宽断⾔的正则中不能含有不定长的表达式,改⼀下:
s = '<span>Hello world!</span>'
p = repile(r'(?<=<(\w{4})>)(.*)(?=</\1>)')
print'【Output】'
print re.findall(p,s)
【Output】
[('span', 'Hello world!')]
2. 匹配存在多种规则约束(含否定规则)的字符串
匹配⼀个长度为4个字符的字符串,该字符串只能由数字、字母或下划线3种字符组成,且必须包含其中的⾄少两种字符,且不能以下划线或数字开头:
# 测试数据
strs = ['_aaa','1aaa','aaaa','a_12','a1','a_123','1234','____']
p = repile(r'^(?!_)(?!\d)(?!\d+$)(?![a-zA-Z]+$)\w{4}$')
print'【Output】'
for s in strs:
print re.findall(p,s)
【Output】[]
[]
[]
['a_12'] []
[]
[]
[]
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论