[編輯] 無捕獲組和命名組php
精心設計的 REs 也許會用不少組,既能夠捕獲感興趣的子串,又能夠分組和結構化 RE 自己。在複雜的 REs 裏,追蹤組號變得困難。有兩個功能能夠對這個問題有所幫助。它們也都使用正則表達式擴展的通用語法,所以咱們來看看第一個。python
Perl 5 對標準正則表達式增長了幾個附加功能,Python 的 re 模塊也支持其中的大部分。選擇一個新的單按鍵元字符或一個以 "\" 開始的特殊序列來表示新的功能,而又不會使 Perl 正則表達式與標準正則表達式產生混亂是有難度的。若是你選擇 "&" 作爲新的元字符,舉個例子,老的表達式認爲 "&" 是一個正常的字符,而不會在使用 \& 或 [&] 時也不會轉義。正則表達式
Perl 開發人員的解決方法是使用 (?...) 來作爲擴展語法。"?" 在括號後面會直接致使一個語法錯誤,由於 "?" 沒有任何字符能夠重複,所以它不會產生任何兼容問題。緊隨 "?" 之後的字符指出擴展的用途,所以 (?=foo)spring
Python 新增了一個擴展語法到 Perl 擴展語法中。若是在問號後的第一個字符是 "P",你就能夠知道它是針對 Python 的擴展。目前有兩個這樣的擴展: (?P<name>...) 定義一個命名組,(?P=name) 則是對命名組的逆向引用。若是 Perl 5 的將來版本使用不一樣的語法增長了相同的功能,那幺 re 模塊也將改變以支持新的語法,這是爲了兼容性的目的而保持的 Python 專用語法。ubuntu
如今咱們看一下普通的擴展語法,咱們回過頭來簡化在複雜 REs 中使用組運行的特性。由於組是從左到右編號的,並且一個複雜的表達式也許會使用許多組,它可使跟蹤當前組號變得困難,而修改如此複雜的 RE 是十分麻煩的。在開始時插入一個新組,你能夠改變它之後的每一個組號。設計
首先,有時你想用一個組去收集正則表達式的一部分,但又對組的內容不感興趣。你能夠用一個無捕獲組: (?:...) 來實現這項功能,這樣你能夠在括號中發送任何其餘正則表達式。開發
#!python
>>> m = re.match("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.match("(?:[abc])+", "abc")
>>> m.groups()
()字符串
除了捕獲匹配組的內容以外,無捕獲組與捕獲組表現徹底同樣;你能夠在其中放置任何字符,能夠用重複元字符如 "*" 來重複它,能夠在其餘組(無捕獲組與捕獲組)中嵌套它。(?:...) 對於修改已有組尤爲有用,由於你能夠不用改變全部其餘組號的狀況下添加一個新組。捕獲組和無捕獲組在搜索效率方面也沒什麼不一樣,沒有哪個比另外一個更快。get
其次,更重要和強大的是命名組;與用數字指定組不一樣的是,它能夠用名字來指定。it
命令組的語法是 Python 專用擴展之一: (?P<name>...)。名字很明顯是組的名字。除了該組有個名字以外,命名組也同捕獲組是相同的。`MatchObject` 的方法處理捕獲組時接受的要麼是表示組號的整數,要麼是包含組名的字符串。命名組也能夠是數字,因此你能夠經過兩種方式來獲得一個組的信息:
#!python
>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'
命名組是便於使用的,由於它可讓你使用容易記住的名字來代替不得不記住的數字。這裏有一個來自 imaplib 模塊的 RE 示例:
#!python
InternalDate = re.compile(r'INTERNALDATE "'
r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
r'(?P<year>[0-9][0-9][0-9][0-9])'
r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
r'"')
很明顯,獲得 m.group('zonem') 要比記住獲得組 9 要容易得多。
由於逆向引用的語法,象 (...)\1 這樣的表達式所表示的是組號,這時用組名代替組號天然會有差異。還有一個 Python 擴展:(?P=name) ,它可使叫 name 的組內容再次在當前位置發現。正則表達式爲了找到重複的單詞,(\b\w+)\s+\1 也能夠被寫成 (?P<word>\b\w+)\s+(?P=word):
#!python
>>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
>>> p.search('Paris in the the spring').group()
'the the'[編輯] 前向界定符
另外一個零寬界定符(zero-width assertion)是前向界定符。前向界定符包括前向確定界定符和後向確定界定符,所下所示:
(?=...)
前向確定界定符。若是所含正則表達式,以 ... 表示,在當前位置成功匹配時成功,不然失敗。但一旦所含表達式已經嘗試,匹配引擎根本沒有提升;模式的剩餘部分還要嘗試界定符的右邊。
(?!...)
前向否認界定符。與確定界定符相反;當所含表達式不能在字符串當前位置匹配時成功
經過示範在哪前向能夠成功有助於具體實現。考慮一個簡單的模式用於匹配一個文件名,並將其經過 "." 分紅基本名和擴展名兩部分。如在 "news.rc" 中,"news" 是基本名,"rc" 是文件的擴展名。
匹配模式很是簡單:
.*[.].*$
注意 "." 須要特殊對待,由於它是一個元字符;我把它放在一個字符類中。另外注意後面的 $; 添加這個是爲了確保字符串全部的剩餘部分必須被包含在擴展名中。這個正則表達式匹配 "foo.bar"、"autoexec.bat"、 "sendmail.cf" 和 "printers.conf"。
如今,考慮把問題變得複雜點;若是你想匹配的擴展名不是 "bat" 的文件名?一些不正確的嘗試:
.*[.][^b].*$
上面的第一次去除 "bat" 的嘗試是要求擴展名的第一個字符不是 "b"。這是錯誤的,由於該模式也不能匹配 "foo.bar"。
.*[.]([<sup>b]..|.[^a].|..[</sup>t])$
當你試着修補第一個解決方法而要求匹配下列狀況之一時表達式更亂了:擴展名的第一個字符不是 "b"; 第二個字符不是 "a";或第三個字符不是 "t"。這樣能夠接受 "foo.bar" 而拒絕 "autoexec.bat",但這要求只能是三個字符的擴展名而不接受兩個字符的擴展名如 "sendmail.cf"。咱們將在努力修補它時再次把該模式變得複雜。
.*[.]([<sup>b].?.?|.[^a]?.?|..?[</sup>t]?)$
在第三次嘗試中,第二和第三個字母都變成可選,爲的是容許匹配比三個字符更短的擴展名,如 "sendmail.cf"。
該模式如今變得很是複雜,這使它很難讀懂。更糟的是,若是問題變化了,你想擴展名不是 "bat" 和 "exe",該模式甚至會變得更復雜和混亂。
前向否認把全部這些裁剪成:
.*[.](?!bat$).*$
前向的意思:若是表達式 bat 在這裏沒有匹配,嘗試模式的其餘部分;若是 bat$ 匹配,整個模式將失敗。後面的 $ 被要求是爲了確保象 "sample.batch" 這樣擴展名以 "bat" 開頭的會被容許。
將另外一個文件擴展名排除在外如今也容易;簡單地將其作爲可選項放在界定符中。下面的這個模式將以 "bat" 或 "exe" 結尾的文件名排除在外。
.*[.](?!bat$|exe$).*$