python正則表達式系列(5)——零寬斷言

本文主要總結了python正則零寬斷言(zero-length-assertion)的一些常用用法。

1. 什麼是零寬斷言

有時候在使用正則表達式做匹配的時候,我們希望匹配一個字符串,這個字符串的前面或後面需要是特定的內容,但我們又不想要前面或後面的這個特定的內容,這時候就需要零寬斷言的幫助了。所謂零寬斷言,簡單來說就是匹配一個位置,這個位置滿足某個正則,但是不納入匹配結果的,所以叫“零寬”,而且這個位置的前面或後面需要滿足某種正則。

比如對於一個字符串:”finished going done doing”,我們希望匹配出其中的以ing結尾的單詞,就可以使用零寬斷言:

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‘兩個單詞,達到目的。

這裏正則中使用的(?=ing\b)就是一種零寬斷言,它匹配這樣一個位置:這個位置有一個’ing’字符串,後面跟着一個’\b’符號,並且這個位置前面的字符串滿足正則:\b\w+,於是匹配結果就是:['go','do']

2. 不同的零寬斷言

零寬斷言分爲四種:正預測先行斷言、正回顧後發斷言、負預測先行斷言、負回顧後發斷言,不同的斷言匹配的位置不同。

總結一下,這幾個彷彿說的不是”人話”的令人費解的名詞可以這樣理解:其中的“正”指的是肯定預測,即某個位置滿足某個正則,而與之對應的“負”則指的是否定預測,即某個位置不要滿足某個正則;其中的“預測先行”則指的是“往後看”,“先往後走”的意思,即這個位置是出現在某一個字符串後面的,而與之相反的“回顧後發”則指的是相反的意思:“往前看”,即匹配的這個位置是出現在某個字符串的前面的。

不理解沒關係,我們用實例說話,下面對每種零寬斷言進行詳細介紹。

1. 正預測先行斷言:(?=exp)

匹配一個位置(但結果不包含此位置)之前的文本內容,這個位置滿足正則exp,舉例:匹配出字符串s中以ing結尾的單詞的前半部分:

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. 正回顧後發斷言:(?<=exp)

匹配一個位置(但結果不包含此位置)之後的文本,這個位置滿足正則exp,舉例:匹配出字符串s中以do開頭的單詞的後半部分:

s = "doing done do todo"
p = re.compile(r'(?<=\bdo)\w+\b')

print '【Output】'
print re.findall(p,s)
【Output】
['ing', 'ne']

3. 負預測先行斷言:(?!exp)

匹配一個位置(但結果不包含此位置)之前的文本,此位置不能滿足正則exp,舉例:匹配出字符串s中不以ing結尾的單詞的前半部分:

s = 'done run going'
p = re.compile(r'\b\w+(?!ing\b)')

print '【Output】'
print re.findall(p,s)
【Output】
['done', 'run', 'going']

可見,出問題了,這不是我們預期的結果(預期的結果是:done和run),這是因爲負向斷言不支持匹配不定長的表達式,將p改一下再匹配:

s = 'done run going'
p = re.compile(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 = re.compile(r'(?<!\bdo)\w+\b')

print '【Output】'
print re.findall(p,s)
【Output】
['done', 'run', 'going']

可見也存在與負預測先行斷言相同的問題,改一下:

s = 'done run going'
p = re.compile(r'(?<!\bdo)\w{2}\b')

print '【Output】'
print re.findall(p,s)
【Output】
['un', 'ng']

5. 正向零寬斷言的結合使用

舉例:字符串ip是一個ip地址,現在要匹配出其中的四個整數:

ip = '160.158.0.77'
p = re.compile(r'(?<=\.)?\d+(?=\.)?')

print '【Output】'
print re.findall(p,ip)
【Output】
['160', '158', '0', '77']

6. 負向零寬斷言的結合使用

舉例:匹配字符串s中的一些單詞,這些單詞不以’x’開頭且不以’y’結尾:

s = 'xaay xbbc accd'
p = re.compile(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 = re.compile(r'(?<=<(?:\w+)>(.*)(?=</\1>))')

print '【Output】'
print re.findall(p,s)
# 報錯:error: look-behind requires fixed-width pattern

上面的報錯是因爲零寬斷言的正則中不能含有不定長的表達式,改一下:

s = '<span>Hello world!</span>'
p = re.compile(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 = re.compile(r'^(?!_)(?!\d)(?!\d+$)(?![a-zA-Z]+$)\w{4}$')

print '【Output】'
for s in strs:
    print re.findall(p,s)
【Output】
[]
[]
[]
['a_12']
[]
[]
[]
[]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章