非捕獲組命名組
精心設計的正則表達式可能會劃分不少組,這些組不只能夠匹配相關的子串,還可以對正則表達式自己進行分組和結構化。在複雜的正則表達式中,因爲有太多的組,所以經過組的序號來跟蹤和使用會變得困難。有兩個新的功能能夠幫你解決這個問題——非捕獲組和命名組——它們都使用了一個公共的正則表達式擴展語法。咱們先來看看這個表達式擴展語法是什麼。
正則表達式的擴展語法
衆所周知,Perl 5 爲標準的正則表達式增長了許多強大的功能。Perl 的開發者們並不能選擇一個新的元字符或者經過反斜槓構造一個新的特殊序列來實現擴展的功能。由於這樣會和標準的正則表達式發生衝突。好比你想選擇 & 做爲擴展功能的元字符(在標準正則表達式中,& 沒有特殊意義),但這樣的話,已經按照標準語法寫出來的正則表達式就不得不修改,由於它們中包含的 '&' 意願上只是把它當作普通字符來匹配而已。
小甲魚解釋:看起來非常頭疼的兼容性問題,Perl 的開發者們是如何解決的呢?請接着看......
最終,Perl 的開發者們決定使用 (?...) 做爲擴展語法。問號 ? 緊跟在左小括號 ( 後邊,自己是一個語法錯誤的寫法,由於 ?前邊沒有東西能夠重複,因此這樣就解決了兼容性的問題(理由是語法正確的正則表達式確定不會這麼寫嘛~)。而後,緊跟在 ? 後邊的字符則表示哪些擴展語法會被使用。例如 (?=foo) 表示一種新的擴展功能(前向斷言),(?:foo) 則表示另外一種擴展功能(一個包含子串 foo 的非捕獲組)。
Python 支持 Perl 的一些擴展語法,而且在此基礎上還增長了一個擴展語法。若是緊跟在問號 ? 後邊的是 P,那麼能夠確定這是一個 Python 的擴展語法。
好,既然咱們已經知道了如何對正則表達式的標準語法進行擴展,那咱們回來看看這些擴展語法在複雜的正則表達式中是如何應用的。
非捕獲組
第一個咱們要講的是非捕獲組。有時候你知識須要用一個組來表示部分正則表達式,你並不須要這個組去匹配任何東西,這時你能夠經過非捕獲組來明確表示你的意圖。非捕獲組的語法是 (?:...),這個 ... 你能夠替換爲任何正則表達式。
正則表達式
小甲魚解釋:「捕獲」就是匹配的意思啦,普通的子組都是捕獲組,由於它們能從字符串中匹配到數據。
除了你不能從非捕獲組得到匹配的內容以外,其餘的非捕獲組跟普通子組沒有什麼區別了。你能夠在裏邊聽任何東西,使用重複功能的元字符,或者跟其餘子組進行嵌套(捕獲的或者非捕獲的子組均可以)。
當你須要修改一個現有的模式的時候,(?:...) 是很是有用的。原始是添加一個非捕獲組並不會影響到其餘(捕獲)組的序號。值得一提的是,在搜索的速度上,捕獲組和非捕獲組的速度是沒有任何區別的。
命名組
咱們再來看另一個重要功能:命名組。普通子組咱們使用序列來訪問它們,命名組則可使用一個有意義的名字來進行訪問。
命名組的語法是 Python 特有的擴展語法:(?P<name>)。很明顯,< > 裏邊的 name 就是命名組的名字啦。命名組除了有一個名字標識以外,跟其餘捕獲組是同樣的。
匹配對象的全部方法不只能夠處理那些由數字引用的捕獲組,還能夠處理經過字符串引用的命名組。除了使用名字訪問,命名組仍然可使用數字序號進行訪問:
spring
命名組很是好用,由於它讓你可使用一個好記的名字代替一些毫無心義的數字。下邊是來自 imaplib 模塊的例子:
spa
很明顯,使用 m.group('zonem') 訪問匹配內容要比使用數字 9 更簡單明瞭。
正則表達式中,反向引用的語法像 (...)\1 是使用序號的方式來訪問子組;在命名組裏,顯然也是有對應的變體:使用名字來代替序號。其擴展語法是 (?P=name),含義是該 name 指向的組須要在當前位置再次引用。那麼搜索兩個單詞的正則表達式能夠寫成 (\b\w+)\s+\1,也能夠寫成 (?P<word>\b\w+)\s+(?P=word):
設計
前向斷言
咱們要講解的另外一個零寬斷言是前向斷言,前向斷言能夠分爲前向確定斷言和前向否認斷言兩種形式。
(?=...)code
前向確定斷言。若是當前包含的正則表達式(這裏以 ... 表示)在當前位置成功匹配,則表明成功,不然失敗。一旦該部分正則表達式被匹配引擎嘗試過,就不會繼續進行匹配了;剩下的模式在此斷言開始的地方繼續嘗試。對象
(?!...)開發
前向否認斷言。這跟前向確定斷言相反(不匹配則表示成功,匹配表示失敗)。字符串
爲了使你們更易懂,咱們舉個例子來證實這玩意是真的頗有用。你們考慮一個簡單的正則表達式模式,這個模式的做用是匹配一個文件名。咱們都知道,文件名是用 . 將名字和擴展名分隔開的。例如在 fishc.txt 中,fishc 是文件的名字,.txt 是擴展名。
這個正則表達式其實挺簡單的:
.*[.].*$
注意,這裏用於分隔的 . 是一個元字符,因此咱們使用 [.] 剝奪了它的特殊功能。還有 $,咱們使用 $ 確保字符串剩餘的部分都包含在擴展名中。因此這個正則表達式能夠匹配 fishc.txt,foo.bar,autoexec.bat,sendmail.cf,printers.conf 等。
如今咱們來考慮一種複雜一點的狀況,若是你想匹配擴展名不是 bat 的文件,你的正則表達式應該怎麼寫呢?
咱們先來看下你有可能寫錯的嘗試:
.*[.][^b].*$
這裏爲了排除 bat,咱們先嚐試排除擴展名的第一個字符爲非 b。但這是錯誤的開始,由於 foo.bar 後綴名的第一個字符也是 b。
爲了彌補剛剛的錯誤,咱們試了這一招:
.*[.]([^b]..|.[^a].|..[^t])$
咱們不得不認可,這個正則表達式變得很難看......但這樣第一個字符不是 b,第二個字符不是 a,第三個字符不是 t......這樣正好能夠接受 foo.bar,排除 autoexec.bat。但問題又來了,這樣的正則表達式要求擴展名必須是三個字符,好比sendmail.cf 就會被排除掉。
好吧,咱們接着修復問題:
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
在第三次嘗試中,咱們讓第二個和第三個字符變成可選的。這樣就能夠匹配稍短的擴展名,好比 sendmail.cf。
不得不認可,咱們把事情搞砸了,如今的正則表達式變得艱澀難懂外加奇醜無比!!
更慘的是若是需求改變了,例如你想同時排除 bat 和 exe 擴展名,這個正則表達式模式就變得更加複雜了......
噹噹噹當!主角登場,其實,一個前向否認斷言就能夠解決你的難題:
.*[.](?!bat$).*$
咱們來解釋一下這個前向否認斷言的含義:若是正則表達式 bat 在當前位置不匹配,嘗試剩下的部分正則表達式;若是 bat匹配成功,整個正則表達式將會失敗(由於是前向否認斷言嘛^_^)。(?!bat$) 末尾的 $ 是爲了確保能夠正常匹配像sample.batch 這種以 bat 開始的擴展名。
一樣,有了前向否認斷言,要同時排除 bat 和 exe 擴展名,也變得至關容易:
.*[.](?!bat$|exe$).*$
io