本文主要總結(jié)了python正則零寬斷言(zero-length-assertion)的一些常用用法冠句。
1. 什么是零寬斷言
有時(shí)候在使用正則表達(dá)式做匹配的時(shí)候,我們希望匹配一個(gè)字符串幸乒,這個(gè)字符串的前面或后面需要是特定的內(nèi)容懦底,但我們又不想要前面或后面的這個(gè)特定的內(nèi)容,這時(shí)候就需要零寬斷言的幫助了罕扎。所謂零寬斷言聚唐,簡(jiǎn)單來(lái)說(shuō)就是匹配一個(gè)位置,這個(gè)位置滿(mǎn)足某個(gè)正則腔召,但是不納入匹配結(jié)果的杆查,所以叫“零寬”,而且這個(gè)位置的前面或后面需要滿(mǎn)足某種正則臀蛛。
比如對(duì)于一個(gè)字符串:finished going done doing
亲桦,我們希望匹配出其中的以ing
結(jié)尾的單詞崖蜜,就可以使用零寬斷言:
import re
s = 'finished going done doing'
p = re.compile(r'\b\w+(?=ing\b)')
print '【Output】'
print [x + 'ing' for x in re.findall(p,s)]
【Output】
['going', 'doing']
可以看出從中匹配出了going
和doing
兩個(gè)單詞,達(dá)到目的客峭。
這里正則中使用的(?=ing\b)
就是一種零寬斷言豫领,它匹配這樣一個(gè)位置:這個(gè)位置有一個(gè)ing
字符串,后面跟著一個(gè)\b
符號(hào)桃笙,并且這個(gè)位置前面的字符串滿(mǎn)足正則:\b\w+
氏堤,于是匹配結(jié)果就是:['go','do']
2. 不同的零寬斷言
零寬斷言分為四種:正預(yù)測(cè)先行斷言、正回顧后發(fā)斷言搏明、負(fù)預(yù)測(cè)先行斷言鼠锈、負(fù)回顧后發(fā)斷言,不同的斷言匹配的位置不同星著。
總結(jié)一下购笆,這幾個(gè)仿佛說(shuō)的不是"人話(huà)"的令人費(fèi)解的名詞可以這樣理解:其中的“正”指的是肯定預(yù)測(cè),即某個(gè)位置滿(mǎn)足某個(gè)正則虚循,而與之對(duì)應(yīng)的“負(fù)”則指的是否定預(yù)測(cè)同欠,即某個(gè)位置不要滿(mǎn)足某個(gè)正則;其中的“預(yù)測(cè)先行”則指的是“往后看”横缔,“先往后走”的意思铺遂,即這個(gè)位置是出現(xiàn)在某一個(gè)字符串后面的,而與之相反的“回顧后發(fā)”則指的是相反的意思:“往前看”茎刚,即匹配的這個(gè)位置是出現(xiàn)在某個(gè)字符串的前面的襟锐。
不理解沒(méi)關(guān)系,我們用實(shí)例說(shuō)話(huà)膛锭,下面對(duì)每種零寬斷言進(jìn)行詳細(xì)介紹粮坞。
1. 正預(yù)測(cè)先行斷言:(?=exp)
匹配一個(gè)位置(但結(jié)果不包含此位置)之前的文本內(nèi)容,這個(gè)位置滿(mǎn)足正則exp初狰,舉例:匹配出字符串s中以ing結(jié)尾的單詞的前半部分:
s = "I'm singing while you're dancing."
p = re.compile(r'\b\w+(?=ing\b)')
print '【Output】'
print re.findall(p,s)
【Output】
['sing', 'danc']
2. 正回顧后發(fā)斷言:(?<=exp)
匹配一個(gè)位置(但結(jié)果不包含此位置)之后的文本莫杈,這個(gè)位置滿(mǎn)足正則exp,舉例:匹配出字符串s中以do開(kāi)頭的單詞的后半部分:
s = "doing done do todo"
p = re.compile(r'(?<=\bdo)\w+\b')
print '【Output】'
print re.findall(p,s)
【Output】
['ing', 'ne']
3. 負(fù)預(yù)測(cè)先行斷言:(?!exp)
匹配一個(gè)位置(但結(jié)果不包含此位置)之前的文本奢入,此位置不能滿(mǎn)足正則exp筝闹,舉例:匹配出字符串s中不以ing結(jié)尾的單詞的前半部分:
s = 'done run going'
p = re.compile(r'\b\w+(?!ing\b)')
print '【Output】'
print re.findall(p,s)
【Output】
['done', 'run', 'going']
可見(jiàn),出問(wèn)題了俊马,這不是我們預(yù)期的結(jié)果(預(yù)期的結(jié)果是:done和run)丁存,這是因?yàn)樨?fù)向斷言不支持匹配不定長(zhǎng)的表達(dá)式,將p改一下再匹配:
s = 'done run going'
p = re.compile(r'\b\w{2}(?!ing\b)')
print '【Output】'
print re.findall(p,s)
【Output】
['do', 'ru']
可見(jiàn)一次只能匹配出固定長(zhǎng)度的不以ing結(jié)尾的單詞柴我,沒(méi)有完全達(dá)到預(yù)期。這個(gè)問(wèn)題還有待解決扩然。
4. 負(fù)回顧后發(fā)斷言:(?<!exp)
匹配一個(gè)位置(但結(jié)果不包含此位置)之后的文本艘儒,這個(gè)位置不能滿(mǎn)足正則exp,舉例:匹配字符串s中不以do開(kāi)頭的單詞:
s = 'done run going'
p = re.compile(r'(?<!\bdo)\w+\b')
print '【Output】'
print re.findall(p,s)
【Output】
['done', 'run', 'going']
可見(jiàn)也存在與負(fù)預(yù)測(cè)先行斷言相同的問(wèn)題,改一下:
s = 'done run going'
p = re.compile(r'(?<!\bdo)\w{2}\b')
print '【Output】'
print re.findall(p,s)
【Output】
['un', 'ng']
5. 正向零寬斷言的結(jié)合使用
舉例:字符串ip是一個(gè)ip地址界睁,現(xiàn)在要匹配出其中的四個(gè)整數(shù):
ip = '160.158.0.77'
p = re.compile(r'(?<=\.)?\d+(?=\.)?')
print '【Output】'
print re.findall(p,ip)
【Output】
['160', '158', '0', '77']
6. 負(fù)向零寬斷言的結(jié)合使用
舉例:匹配字符串s中的一些單詞觉增,這些單詞不以x
開(kāi)頭且不以y
結(jié)尾:
s = 'xaay xbbc accd'
p = re.compile(r'(?<!\bx)\w+(?!y\b)')
print '【Output】'
print re.findall(p,s)
【Output】
['xaay', 'xbbc', 'accd']
可見(jiàn)這里因?yàn)樨?fù)向斷言不支持不定長(zhǎng)表達(dá)式,所以也存在和前面相同的問(wèn)題翻斟。
3. 零寬斷言的應(yīng)用
1. 匹配html標(biāo)簽之間的內(nèi)容
s = '<span>Hello world!</span>'
p = re.compile(r'(?<=<(?:\w+)>(.*)(?=</\1>))')
print '【Output】'
print re.findall(p,s)
# 報(bào)錯(cuò):error: look-behind requires fixed-width pattern
上面的報(bào)錯(cuò)是因?yàn)榱銓挃嘌缘恼齽t中不能含有不定長(zhǎng)的表達(dá)式逾礁,改一下:
s = '<span>Hello world!</span>'
p = re.compile(r'(?<=<(\w{4})>)(.*)(?=</\1>)')
print '【Output】'
print re.findall(p,s)
【Output】
[('span', 'Hello world!')]
2. 匹配存在多種規(guī)則約束(含否定規(guī)則)的字符串
匹配一個(gè)長(zhǎng)度為4個(gè)字符的字符串,該字符串只能由數(shù)字访惜、字母或下劃線(xiàn)3種字符組成嘹履,且必須包含其中的至少兩種字符,且不能以下劃線(xiàn)或數(shù)字開(kāi)頭:
# 測(cè)試數(shù)據(jù)
strs = ['_aaa','1aaa','aaaa','a_12','a1','a_123','1234','____']
p = re.compile(r'^(?!_)(?!\d)(?!\d+$)(?![a-zA-Z]+$)\w{4}$')
print '【Output】'
for s in strs:
print re.findall(p,s)
【Output】
[]
[]
[]
['a_12']
[]
[]
[]
[]
3. 注意點(diǎn)
零寬斷言雖然也是用小括號(hào)括起來(lái)的债热,但不占用分組的默認(rèn)命名空間砾嫉。舉例如下:
s = 'goingxxx'
# 在緊跟'ing'后面的字符串前加上'AAA'
print re.sub(r'(?<=ing)(\w+)\b',r'AAA\1',s)
# 輸出: goingAAAxxx