摘要 php
本文是經過Python的 re 模塊來使用正則表達式的一個入門教程,和庫參考手冊的對應章節相比,更爲淺顯易懂、按部就班。 html
本文能夠從 http://www.amk.ca/python/howto 捕獲 python
目錄 程序員
目錄[隱藏] |
Python 自1.5版本起增長了re 模塊,它提供 Perl 風格的正則表達式模式。Python 1.5以前版本則是經過 regex 模塊提供 Emacs 風格的模式。Emacs 風格模式可讀性稍差些,並且功能也不強,所以編寫新代碼時儘可能不要再使用 regex 模塊,固然偶爾你仍是可能在老代碼裏發現其蹤跡。 正則表達式
就其本質而言,正則表達式(或 RE)是一種小型的、高度專業化的編程語言,(在Python中)它內嵌在Python中,並經過 re 模塊實現。使用這個小型語言,你能夠爲想要匹配的相應字符串集指定規則;該字符串集可能包含英文語句、e-mail地址、TeX命令或任何你想搞定的東西。而後你能夠問諸如「這個字符串匹配該模式嗎?」或「在這個字符串中是否有部分匹配該模式呢?」。你也可使用 RE 以各類方式來修改或分割字符串。 spring
正則表達式模式被編譯成一系列的字節碼,而後由用 C 編寫的匹配引擎執行。在高級用法中,也許還要仔細留意引擎是如何執行給定 RE ,如何以特定方式編寫 RE 以令生產的字節碼運行速度更快。本文並不涉及優化,由於那要求你已充分掌握了匹配引擎的內部機制。 編程
正則表達式語言相對小型和受限(功能有限),所以並不是全部字符串處理都能用正則表達式完成。固然也有些任務能夠用正則表達式完成,不過最終表達式會變得異常複雜。碰到這些情形時,編寫 Python 代碼進行處理可能反而更好;儘管 Python 代碼比一個精巧的正則表達式要慢些,但它更易理解。 api
字符 | 描述 |
---|---|
\ | 將下一個字符標記爲一個特殊字符、或一個原義字符、或一個 後向引用、或一個八進制轉義符。例如,'n' 匹配字符 "n"。'\n' 匹配一個換行符。序列 '\\' 匹配 "\" 而 "\(" 則匹配 "("。 |
^ | 匹配輸入字符串的開始位置。若是設置了 RegExp 對象的 Multiline 屬性,^ 也匹配 '\n' 或 '\r' 以後的位置。 |
$ | 匹配輸入字符串的結束位置。若是設置了RegExp 對象的 Multiline 屬性,$ 也匹配 '\n' 或 '\r' 以前的位置。 |
* | 匹配前面的子表達式零次或屢次。例如,zo* 能匹配 "z" 以及 "zoo"。 * 等價於{0,}。 |
+ | 匹配前面的子表達式一次或屢次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價於 {1,}。 |
? | 匹配前面的子表達式零次或一次。例如,"do(es)?" 能夠匹配 "do" 或 "does" 中的"do" 。? 等價於 {0,1}。 |
{n} | n 是一個非負整數。匹配肯定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',可是能匹配 "food" 中的兩個 o。 |
{n,} | n 是一個非負整數。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的全部 o。'o{1,}' 等價於 'o+'。'o{0,}' 則等價於 'o*'。 |
{n,m} | m 和 n 均爲非負整數,其中n <= m。最少匹配 n 次且最多匹配 m 次。劉, "o{1,3}" 將匹配 "fooooood" 中的前三個 o。'o{0,1}' 等價於 'o?'。請注意在逗號和兩個數之間不能有空格。 |
? | 當該字符緊跟在任何一個其餘限制符 (*, +, ?, {n}, {n,}, {n,m}) 後面時,匹配模式是非貪婪的。非貪婪模式儘量少的匹配所搜索的字符串,而默認的貪婪模式則儘量多的匹配所搜索的字符串。例如,對於字符串 "oooo",'o+?' 將匹配單個 "o",而 'o+' 將匹配全部 'o'。 |
. | 匹配除 "\n" 以外的任何單個字符。要匹配包括 '\n' 在內的任何字符,請使用象 '[.\n]' 的模式。 |
(pattern) | 匹配pattern 並獲取這一匹配。所獲取的匹配能夠從產生的 Matches 集合獲得,在VBScript 中使用 SubMatches 集合,在Visual Basic Scripting Edition 中則使用 $0…$9 屬性。要匹配圓括號字符,請使用 '\(' 或 '\)'。 |
(?:pattern) | 匹配 pattern 但不獲取匹配結果,也就是說這是一個非獲取匹配,不進行存儲供之後使用。這在使用 "或" 字符 (|) 來組合一個模式的各個部分是頗有用。例如, 'industr(?:y|ies) 就是一個比 'industry|industries' 更簡略的表達式。 |
(?=pattern) 緩存 (?<=pattern) socket |
正向預查,在任何匹配 pattern 的字符串開始處匹配查找字符串。這是一個非獲取匹配,也就是說,該匹配不須要獲取供之後使用。例如, 'Windows (?=95|98|NT|2000)' 能匹配 "Windows 2000" 中的 "Windows" ,但不能匹配 "Windows 3.1" 中的 "Windows"。預查不消耗字符,也就是說,在一個匹配發生後,在最後一次匹配以後當即開始下一次匹配的搜索,而不是從包含預查的字符以後開始。 (?<=my|your)Windows 能夠匹配 my Windows或者your Windows |
(?!pattern) (?<!pattern) |
負向預查,在任何不匹配Negative lookahead matches the search string at any point where a string not matching pattern 的字符串開始處匹配查找字符串。這是一個非獲取匹配,也就是說,該匹配不須要獲取供之後使用。例如'Windows (?!95|98|NT|2000)' 能匹配 "Windows 3.1" 中的 "Windows",但不能匹配 "Windows 2000" 中的 "Windows"。預查不消耗字符,也就是說,在一個匹配發生後,在最後一次匹配以後當即開始下一次匹配的搜索,而不是從包含預查的字符以後開始 。 (?<!my|your)Windows 能夠匹配his Windows,不匹配my Windows和your Windows |
x|y | 匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 則匹配 "zood" 或 "food"。 |
[xyz] | 字符集合。匹配所包含的任意一個字符。例如, '[abc]' 能夠匹配 "plain" 中的 'a'。 |
[^xyz] | 負值字符集合。匹配未包含的任意字符。例如, '[^abc]' 能夠匹配 "plain" 中的'p'。 |
[a-z] | 字符範圍。匹配指定範圍內的任意字符。例如,'[a-z]' 能夠匹配 'a' 到 'z' 範圍內的任意小寫字母字符。 |
[^a-z] | 負值字符範圍。匹配任何不在指定範圍內的任意字符。例如,'[^a-z]' 能夠匹配任何不在 'a' 到 'z' 範圍內的任意字符。 |
\b | 匹配一個單詞邊界,也就是指單詞和空格間的位置。例如, 'er\b' 能夠匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 |
\B | 匹配非單詞邊界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。 |
\cx | 匹配由x指明的控制字符。例如, \cM 匹配一個 Control-M 或回車符。 x 的值必須爲 A-Z 或 a-z 之一。不然,將 c 視爲一個原義的 'c' 字符。 |
\d | 匹配一個數字字符。等價於 [0-9]。 |
\D | 匹配一個非數字字符。等價於 [^0-9]。 |
\f | 匹配一個換頁符。等價於 \x0c 和 \cL。 |
\n | 匹配一個換行符。等價於 \x0a 和 \cJ。 |
\r | 匹配一個回車符。等價於 \x0d 和 \cM。 |
\s | 匹配任何空白字符,包括空格、製表符、換頁符等等。等價於 [ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等價於 [^ \f\n\r\t\v]。 |
\t | 匹配一個製表符。等價於 \x09 和 \cI。 |
\v | 匹配一個垂直製表符。等價於 \x0b 和 \cK。 |
\w | 匹配包括下劃線的任何單詞字符。等價於'[A-Za-z0-9_]'。 |
\W | 匹配任何非單詞字符。等價於 '[^A-Za-z0-9_]'。 |
\xn | 匹配 n,其中 n 爲十六進制轉義值。十六進制轉義值必須爲肯定的兩個數字長。例如, '\x41' 匹配 "A"。'\x041' 則等價於 '\x04' & "1"。正則表達式中可使用 ASCII 編碼。. |
\num | 匹配 num,其中 num 是一個正整數。對所獲取的匹配的引用。例如,'(.)\1' 匹配兩個連續的相同字符。 |
\n | 標識一個八進制轉義值或一個後向引用。若是 \n 以前至少 n 個獲取的子表達式,則 n 爲後向引用。不然,若是 n 爲八進制數字 (0-7),則 n 爲一個八進制轉義值。 |
\nm | 標識一個八進制轉義值或一個後向引用。若是 \nm 以前至少有is preceded by at least nm 個獲取得子表達式,則 nm 爲後向引用。若是 \nm 以前至少有 n 個獲取,則 n 爲一個後跟文字 m 的後向引用。若是前面的條件都不知足,若 n 和 m 均爲八進制數字 (0-7),則 \nm 將匹配八進制轉義值 nm。 |
\nml | 若是 n 爲八進制數字 (0-3),且 m 和 l 均爲八進制數字 (0-7),則匹配八進制轉義值 nml。 |
\un | 匹配 n,其中 n 是一個用四個十六進制數字表示的 Unicode 字符。例如, \u00A9 匹配版權符號 (?)。 |
咱們將從最簡單的正則表達式學習開始。因爲正則表達式經常使用於字符串操做,那咱們就從最多見的任務:字符匹配 下手。
有關正則表達式底層的計算機科學上的詳細解釋(肯定性和非肯定性有限自動機),你能夠查閱編寫編譯器相關的任何教科書。
大多數字母和字符通常都會和自身匹配。例如,正則表達式 test 會和字符串「test」徹底匹配。(你也可使用大小寫不敏感模式,它還能讓這個 RE 匹配「Test」或「TEST」;稍後會有更多解釋。)
這個規則固然會有例外;有些字符比較特殊,它們和自身並不匹配,而是會代表應和一些特殊的東西匹配,或者它們會影響到 RE 其它部分的重複次數。本文很大篇幅專門討論了各類元字符及其做用。
這裏有一個元字符的完整列表;其含義會在本指南餘下部分進行討論。
. ^ $ * + ? { [ ] \ | ( )
咱們首先考察的元字符是"[" 和 "]"。它們經常使用來指定一個字符類別,所謂字符類別就是你想匹配的一個字符集。字符能夠單個列出,也能夠用「-」號分隔的兩個給定字符來表示一個字符區間。例如,[abc] 將匹配"a", "b", 或 "c"中的任意一個字符;也能夠用區間[a-c]來表示同一字符集,和前者效果一致。若是你只想匹配小寫字母,那麼 RE 應寫成 [a-z].
元字符在類別裏並不起做用。例如,[akm$]將匹配字符"a", "k", "m", 或 "$" 中的任意一個;"$"一般用做元字符,但在字符類別裏,其特性被除去,恢復成普通字符。
你能夠用補集來匹配不在區間範圍內的字符。其作法是把"^"做爲類別的首個字符;其它地方的"^"只會簡單匹配 "^"字符自己。例如,[^5] 將匹配除 "5" 以外的任意字符。
也許最重要的元字符是反斜槓"\"。 作爲 Python 中的字符串字母,反斜槓後面能夠加不一樣的字符以表示不一樣特殊意義。它也能夠用於取消全部的元字符,這樣你就能夠在模式中匹配它們了。舉個例子,若是你須要匹配字符 "[" 或 "\",你能夠在它們以前用反斜槓來取消它們的特殊意義: \[ 或 \\。
一些用 "\" 開始的特殊字符所表示的預約義字符集一般是頗有用的,象數字集,字母集,或其它非空字符集。下列是可用的預設特殊字符:
\d 匹配任何十進制數;它至關於類 [0-9]。 \D 匹配任何非數字字符;它至關於類 [^0-9]。 \s 匹配任何空白字符;它至關於類 [ \t\n\r\f\v]。 \S 匹配任何非空白字符;它至關於類 [^ \t\n\r\f\v]。 \w 匹配任何字母數字字符;它至關於類 [a-zA-Z0-9_]。 \W 匹配任何非字母數字字符;它至關於類 [^a-zA-Z0-9_]。
這樣特殊字符均可以包含在一個字符類中。如,[\s,.]字符類將匹配任何空白字符或","或"."。
本節最後一個元字符是 . 。它匹配除了換行字符外的任何字符,在 alternate 模式(re.DOTALL)下它甚至能夠匹配換行。"." 一般被用於你想匹配「任何字符」的地方。
正則表達式第一件能作的事是可以匹配不定長的字符集,而這是其它能做用在字符串上的方法所不能作到的。 不過,若是那是正則表達式惟一的附加功能的話,那麼它們也就不那麼優秀了。它們的另外一個功能就是你能夠指定正則表達式的一部分的重複次數。
咱們討論的第一個重複功能的元字符是 *。* 並不匹配字母字符 "*";相反,它指定前一個字符能夠被匹配零次或更屢次,而不是隻有一次。
舉個例子,ca*t 將匹配 "ct" (0 個 "a" 字符), "cat" (1 個 "a"), "caaat" (3 個 "a" 字符)等等。RE 引擎有各類來自 C 的整數類型大小的內部限制,以防止它匹配超過20億個 "a" 字符;你也許沒有足夠的內存去建造那麼大的字符串,因此將不會累計到那個限制。
象 * 這樣地重複是「貪婪的」;當重複一個 RE 時,匹配引擎會試着重複儘量多的次數。若是模式的後面部分沒有被匹配,匹配引擎將退回並再次嘗試更小的重複。
一步步的示例可使它更加清晰。讓咱們考慮表達式 a[bcd]*b。它匹配字母 "a",零個或更多個來自類 [bcd]中的字母,最後以 "b" 結尾。如今想想該 RE 對字符串 "abcbd" 的匹配。
Step | Matched | Explanation |
1 | a | a 匹配模式 |
2 | abcbd | 引擎匹配 [bcd]*,並盡其所能匹配到字符串的結尾 |
3 | Failure | 引擎嘗試匹配 b,但當前位置已是字符的最後了,因此失敗 |
4 | abcb | 退回,[bcd]*嘗試少匹配一個字符。 |
5 | Failure | 再次嘗次b,但在當前最後一位字符是"d"。 |
6 | abc | 再次退回,[bcd]*只匹配 "bc"。 |
7 | abcb | 再次嘗試 b ,此次當前位上的字符正好是 "b" |
RE 的結尾部分如今能夠到達了,它匹配 "abcb"。這證實了匹配引擎一開始會盡其所能進行匹配,若是沒有匹配而後就逐步退回並反覆嘗試 RE 剩下來的部分。直到它退回嘗試匹配 [bcd] 到零次爲止,若是隨後仍是失敗,那麼引擎就會認爲該字符串根本沒法匹配 RE 。
另外一個重複元字符是 +,表示匹配一或更屢次。請注意 * 和 + 之間的不一樣;* 匹配零或更屢次,因此根本就能夠不出現,而 + 則要求至少出現一次。用同一個例子,ca+t 就能夠匹配 "cat" (1 個 "a"), "caaat" (3 個 "a"), 但不能匹配 "ct"。
還有更多的限定符。問號 ? 匹配一次或零次;你能夠認爲它用於標識某事物是可選的。例如:home-?brew 匹配 "homebrew" 或 "home-brew"。
最複雜的重複限定符是 {m,n},其中 m 和 n 是十進制整數。該限定符的意思是至少有 m 個重複,至多到 n 個重複。舉個例子,a/{1,3}b 將匹配 "a/b","a//b" 和 "a///b"。它不能匹配 "ab" 由於沒有斜槓,也不能匹配 "a////b" ,由於有四個。
你能夠忽略 m 或 n;由於會爲缺失的值假設一個合理的值。忽略 m 會認爲下邊界是 0,而忽略 n 的結果將是上邊界爲無窮大 -- 其實是先前咱們提到的20億,但這也許同無窮大同樣。
細心的讀者也許注意到其餘三個限定符均可以用這樣方式來表示。 {0,} 等同於 *,{1,} 等同於 +,而{0,1}則與 ? 相同。若是能夠的話,最好使用 *,+,或?。很簡單由於它們更短也更容易懂。
如今咱們已經看了一些簡單的正則表達式,那麼咱們實際在 Python 中是如何使用它們的呢? re 模塊提供了一個正則表達式引擎的接口,可讓你將 REs 編譯成對象並用它們來進行匹配。
正則表達式被編譯成 `RegexObject` 實例,能夠爲不一樣的操做提供方法,如模式匹配搜索或字符串替換。
#!python >>> import re >>> p = re.compile('ab*') >>> print p <re.RegexObject instance at 80b4150>
re.compile() 也接受可選的標誌參數,經常使用來實現不一樣的特殊功能和語法變動。咱們稍後將查看全部可用的設置,但如今只舉一個例子:
#!python >>> p = re.compile('ab*', re.IGNORECASE)
RE 被作爲一個字符串發送給 re.compile()。REs 被處理成字符串是由於正則表達式不是 Python 語言的核心部分,也沒有爲它建立特定的語法。(應用程序根本就不須要 REs,所以不必包含它們去使語言說明變得臃腫不堪。)而 re 模塊則只是以一個 C 擴展模塊的形式來被 Python 包含,就象 socket 或 zlib 模塊同樣
將 REs 做爲字符串以保證 Python 語言的簡潔,但這樣帶來的一個麻煩就是象下節標題所講的。
在早期規定中,正則表達式用反斜槓字符 ("\") 來表示特殊格式或容許使用特殊字符而不調用它的特殊用法。這就與 Python 在字符串中的那些起相同做用的相同字符產生了衝突。
讓咱們舉例說明,你想寫一個 RE 以匹配字符串 "\section",多是在一個 LATEX 文件查找。爲了要在程序代碼中判斷,首先要寫出想要匹配的字符串。接下來你須要在全部反斜槓和其它元字符前加反斜槓來取消其特殊意義,結果要匹配的字符串就成了"\\section"。 當把這個字符串傳遞給re.compile()時必須仍是"\\section"。然而,做爲Python的字符串實值(string literals)來表示的話,"\\section"中兩個反斜槓還要再次取消特殊意義,最後結果就變成了"\\\\section"。
字符 | 階段 |
\section | 要匹配的字符串 |
\\section | 爲 re.compile 取消反斜槓的特殊意義 |
"\\\\section" | 爲"\\section"的字符串實值(string literals)取消反斜槓的特殊意義 |
簡單地說,爲了匹配一個反斜槓,不得不在 RE 字符串中寫 '\\\\',由於正則表達式中必須是 "\\",而每一個反斜槓在常規的 Python 字符串實值中必須表示成 "\\"。在 REs 中反斜槓的這個重複特性會致使大量重複的反斜槓,並且所生成的字符串也很難懂。
解決的辦法就是爲正則表達式使用 Python 的 raw 字符串表示;在字符串前加個 "r" 反斜槓就不會被任何特殊方式處理,因此 r"\n" 就是包含"\" 和 "n" 的兩個字符,而 "\n" 則是一個字符,表示一個換行。正則表達式一般在 Python 代碼中都是用這種 raw 字符串表示。
常規字符串 | Raw 字符串 |
"ab*" | r"ab*" |
"\\\\section" | r"\\section" |
"\\w+\\s+\\1" | r"\w+\s+\1" |
一旦你有了已經編譯了的正則表達式的對象,你要用它作什麼呢?`RegexObject` 實例有一些方法和屬性。這裏只顯示了最重要的幾個,若是要看完整的列表請查閱 Python Library Reference
方法/屬性 | 做用 |
match() | 決定 RE 是否在字符串剛開始的位置匹配 |
search() | 掃描字符串,找到這個 RE 匹配的位置 |
findall() | 找到 RE 匹配的全部子串,並把它們做爲一個列表返回 |
finditer() | 找到 RE 匹配的全部子串,並把它們做爲一個迭代器返回 |
若是沒有匹配到的話,match() 和 search() 將返回 None。若是成功的話,就會返回一個 `MatchObject` 實例,其中有此次匹配的信息:它是從哪裏開始和結束,它所匹配的子串等等。
你能夠用採用人機對話並用 re 模塊實驗的方式來學習它。若是你有 Tkinter 的話,你也許能夠考慮參考一下 Tools/scripts/redemo.py,一個包含在 Python 發行版裏的示範程序。
首先,運行 Python 解釋器,導入 re 模塊並編譯一個 RE:
#!python Python 2.2.2 (#1, Feb 10 2003, 12:57:01) >>> import re >>> p = re.compile('[a-z]+') >>> p <_sre.SRE_Pattern object at 80c3c28>
如今,你能夠試着用 RE 的 [a-z]+ 去匹配不一樣的字符串。一個空字符串將根本不能匹配,由於 + 的意思是 「一個或更多的重複次數」。 在這種狀況下 match() 將返回 None,由於它使解釋器沒有輸出。你能夠明確地打印出 match() 的結果來弄清這一點。
#!python >>> p.match("") >>> print p.match("") None
如今,讓咱們試着用它來匹配一個字符串,如 "tempo"。這時,match() 將返回一個 MatchObject。所以你能夠將結果保存在變量裏以便後面使用。
#!python >>> m = p.match( 'tempo') >>> print m <_sre.SRE_Match object at 80c4f68>
如今你能夠查詢 `MatchObject` 關於匹配字符串的相關信息了。MatchObject 實例也有幾個方法和屬性;最重要的那些以下所示:
方法/屬性 | 做用 |
group() | 返回被 RE 匹配的字符串 |
start() | 返回匹配開始的位置 |
end() | 返回匹配結束的位置 |
span() | 返回一個元組包含匹配 (開始,結束) 的位置 |
試試這些方法不久就會清楚它們的做用了:
#!python >>> m.group() 'tempo' >>> m.start(), m.end() (0, 5) >>> m.span() (0, 5)
group() 返回 RE 匹配的子串。start() 和 end() 返回匹配開始和結束時的索引。span() 則用單個元組把開始和結束時的索引一塊兒返回。由於匹配方法檢查到若是 RE 在字符串開始處開始匹配,那麼 start() 將老是爲零。然而, `RegexObject` 實例的 search 方法掃描下面的字符串的話,在這種狀況下,匹配開始的位置就也許不是零了。
#!python >>> print p.match('::: message') None >>> m = p.search('::: message') ; print m <re.MatchObject instance at 80c9650> >>> m.group() 'message' >>> m.span() (4, 11)
在實際程序中,最多見的做法是將 `MatchObject` 保存在一個變量裏,而後檢查它是否爲 None,一般以下所示:
#!python p = re.compile( ... ) m = p.match( 'string goes here' ) if m: print 'Match found: ', m.group() else: print 'No match'
兩個 `RegexObject` 方法返回全部匹配模式的子串。findall()返回一個匹配字符串行表:
#!python >>> p = re.compile('\d+') >>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping') ['12', '11', '10']
findall() 在它返回結果時不得不建立一個列表。在 Python 2.2中,也能夠用 finditer() 方法。
#!python >>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...') >>> iterator <callable-iterator object at 0x401833ac> >>> for match in iterator: ... print match.span() ... (0, 2) (22, 24) (29, 31)
你不必定要產生一個 `RegexObject` 對象而後再調用它的方法;re 模塊也提供了頂級函數調用如 match()、search()、sub() 等等。這些函數使用 RE 字符串做爲第一個參數,然後面的參數則與相應 `RegexObject` 的方法參數相同,返回則要麼是 None 要麼就是一個 `MatchObject` 的實例。
#!python >>> print re.match(r'From\s+', 'Fromage amk') None >>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998') <re.MatchObject instance at 80c5978>
Under the hood, 這些函數簡單地產生一個 RegexOject 並在其上調用相應的方法。它們也在緩存裏保存編譯後的對象,所以在未來調用用到相同 RE 時就會更快。
你將使用這些模塊級函數,仍是先獲得一個 `RegexObject` 再調用它的方法呢?如何選擇依賴於怎樣用 RE 更有效率以及你我的編碼風格。若是一個 RE 在代碼中只作用一次的話,那麼模塊級函數也許更方便。若是程序包含不少的正則表達式,或在多處複用同一個的話,那麼將所有定義放在一塊兒,在一段代碼中提早編譯全部的 REs 更有用。從標準庫中看一個例子,這是從 xmllib.py 文件中提取出來的:
#!python ref = re.compile( ... ) entityref = re.compile( ... ) charref = re.compile( ... ) starttagopen = re.compile( ... )
我一般更喜歡使用編譯對象,甚至它只用一次,but few people will be as much of a purist about this as I am。
編譯標誌讓你能夠修改正則表達式的一些運行方式。在 re 模塊中標誌可使用兩個名字,一個是全名如 IGNORECASE,一個是縮寫,一字母形式如 I。(若是你熟悉 Perl 的模式修改,一字母形式使用一樣的字母;例如 re.VERBOSE的縮寫形式是 re.X。)多個標誌能夠經過按位 OR-ing 它們來指定。如 re.I | re.M 被設置成 I 和 M 標誌:
這有個可用標誌表,對每一個標誌後面都有詳細的說明。
標誌 | 含義 |
DOTALL, S | 使 . 匹配包括換行在內的全部字符 |
IGNORECASE, I | 使匹配對大小寫不敏感 |
LOCALE, L | 作本地化識別(locale-aware)匹配 |
MULTILINE, M | 多行匹配,影響 ^ 和 $ |
VERBOSE, X | 可以使用 REs 的 verbose 狀態,使之被組織得更清晰易懂 |
I
IGNORECASE
使匹配對大小寫不敏感;字符類和字符串匹配字母時忽略大小寫。舉個例子,[A-Z]也能夠匹配小寫字母,Spam 能夠匹配 "Spam", "spam", 或 "spAM"。這個小寫字母並不考慮當前位置。
L
LOCALE
影響 \w, \W, \b, 和 \B,這取決於當前的本地化設置。
locales 是 C 語言庫中的一項功能,是用來爲須要考慮不一樣語言的編程提供幫助的。舉個例子,若是你正在處理法文文本,你想用 \w+ 來匹配文字,但 \w 只匹配字符類 [A-Za-z];它並不能匹配 "é" 或 "ç"。若是你的系統配置適當且本地化設置爲法語,那麼內部的 C 函數將告訴程序 "é" 也應該被認爲是一個字母。當在編譯正則表達式時使用 LOCALE 標誌會獲得用這些 C 函數來處理 \w 後的編譯對象;這會更慢,但也會象你但願的那樣能夠用 \w+ 來匹配法文文本。
M
MULTILINE
(此時 ^ 和 $ 不會被解釋; 它們將在 4.1 節被介紹.)
使用 "^" 只匹配字符串的開始,而 $ 則只匹配字符串的結尾和直接在換行前(若是有的話)的字符串結尾。當本標誌指定後, "^" 匹配字符串的開始和字符串中每行的開始。一樣的, $ 元字符匹配字符串結尾和字符串中每行的結尾(直接在每一個換行以前)。
S
DOTALL
使 "." 特殊字符徹底匹配任何字符,包括換行;沒有這個標誌, "." 匹配除了換行外的任何字符。
X
VERBOSE
該標誌經過給予你更靈活的格式以便你將正則表達式寫得更易於理解。當該標誌被指定時,在 RE 字符串中的空白符被忽略,除非該空白符在字符類中或在反斜槓以後;這可讓你更清晰地組織和縮進 RE。它也能夠容許你將註釋寫入 RE,這些註釋會被引擎忽略;註釋用 "#"號 來標識,不過該符號不能在字符串或反斜槓以後。
舉個例子,這裏有一個使用 re.VERBOSE 的 RE;看看讀它輕鬆了多少?
#!python charref = re.compile(r"""&[[]] # Start of a numeric entity reference|||here has wrong.i can't fix ( [0-9]+[^0-9] # Decimal form | 0[0-7]+[^0-7] # Octal form | x[0-9a-fA-F]+[^0-9a-fA-F] # Hexadecimal form ) """, re.VERBOSE)
沒有 verbose 設置, RE 會看起來象這樣:
#!python charref = re.compile("&#([0-9]+[^0-9]" "|0[0-7]+[^0-7]" "|x[0-9a-fA-F]+[^0-9a-fA-F])")
在上面的例子裏,Python 的字符串自動鏈接能夠用來將 RE 分紅更小的部分,但它比用 re.VERBOSE 標誌時更難懂
到目前爲止,咱們只展現了正則表達式的一部分功能。在本節,咱們將展現一些新的元字符和如何使用組來檢索被匹配的文本部分。
==
==
== ==
粗體文字連接標題還有一些咱們還沒展現的元字符,其中的大部分將在本節展現。
剩下來要討論的一部分元字符是零寬界定符(zero-width assertions)。它們並不會使引擎在處理字符串時更快;相反,它們根本就沒有對應任何字符,只是簡單的成功或失敗。舉個例子, \b 是一個在單詞邊界定位當前位置的界定符(assertions),這個位置根本就不會被 \b 改變。這意味着零寬界定符(zero-width assertions)將永遠不會被重複,由於若是它們在給定位置匹配一次,那麼它們很明顯能夠被匹配無數次。
|
可選項,或者 "or" 操做符。若是 A 和 B 是正則表達式,A|B 將匹配任何匹配了 "A" 或 "B" 的字符串。| 的優先級很是低,是爲了當你有多字符串要選擇時能適當地運行。Crow|Servo 將匹配"Crow" 或 "Servo", 而不是 "Cro", 一個 "w" 或 一個 "S", 和 "ervo"。
爲了匹配字母 "|",能夠用 \|,或將其包含在字符類中,如[|]。
^
匹配行首。除非設置 MULTILINE 標誌,它只是匹配字符串的開始。在 MULTILINE 模式裏,它也能夠直接匹配字符串中的每一個換行。
例如,若是你只但願匹配在行首單詞 "From",那麼 RE 將用 ^From。
#!python >>> print re.search('^From', 'From Here to Eternity') <re.MatchObject instance at 80c1520> >>> print re.search('^From', 'Reciting From Memory') None
$
匹配行尾,行尾被定義爲要麼是字符串尾,要麼是一個換行字符後面的任何位置。
#!python >>> print re.search('}$', '{block}') <re.MatchObject instance at 80adfa8> >>> print re.search('}$', '{block} ') None >>> print re.search('}$', '{block}\n') <re.MatchObject instance at 80adfa8>
匹配一個 "$",使用 \$ 或將其包含在字符類中,如[$]。
\A
只匹配字符串首。當不在 MULTILINE 模式,\A 和 ^ 其實是同樣的。然而,在 MULTILINE 模式裏它們是不一樣的;\A 只是匹配字符串首,而 ^ 還能夠匹配在換行符以後字符串的任何位置。
\Z
Matches only at the end of the string.
只匹配字符串尾。
\b
單詞邊界。這是個零寬界定符(zero-width assertions)只用以匹配單詞的詞首和詞尾。單詞被定義爲一個字母數字序列,所以詞尾就是用空白符或非字母數字符來標示的。
下面的例子只匹配 "class" 整個單詞;而當它被包含在其餘單詞中時不匹配。
#!python >>> p = re.compile(r'\bclass\b') >>> print p.search('no class at all') <re.MatchObject instance at 80c8f28> >>> print p.search('the declassified algorithm') None >>> print p.search('one subclass is') None
當用這個特殊序列時你應該記住這裏有兩個微妙之處。第一個是 Python 字符串和正則表達式之間最糟的衝突。在 Python 字符串裏,"\b" 是反斜槓字符,ASCII值是8。若是你沒有使用 raw 字符串時,那麼 Python 將會把 "\b" 轉換成一個回退符,你的 RE 將沒法象你但願的那樣匹配它了。下面的例子看起來和咱們前面的 RE 同樣,但在 RE 字符串前少了一個 "r" 。
#!python >>> p = re.compile('\bclass\b') >>> print p.search('no class at all') None >>> print p.search('\b' + 'class' + '\b') <re.MatchObject instance at 80c3ee0>
第二個在字符類中,這個限定符(assertion)不起做用,\b 表示回退符,以便與 Python 字符串兼容。
\B
另外一個零寬界定符(zero-width assertions),它正好同 \b 相反,只在當前位置不在單詞邊界時匹配。
你常常須要獲得比 RE 是否匹配還要多的信息。正則表達式經常用來分析字符串,編寫一個 RE 匹配感興趣的部分並將其分紅幾個小組。舉個例子,一個 RFC-822 的頭部用 ":" 隔成一個頭部名和一個值,這就能夠經過編寫一個正則表達式匹配整個頭部,用一組匹配頭部名,另外一組匹配頭部值的方式來處理。
組是經過 "(" 和 ")" 元字符來標識的。 "(" 和 ")" 有不少在數學表達式中相同的意思;它們一塊兒把在它們裏面的表達式組成一組。舉個例子,你能夠用重複限制符,象 *, +, ?, 和 {m,n},來重複組裏的內容,好比說(ab)* 將匹配零或更多個重複的 "ab"。
#!python >>> p = re.compile('(ab)*') >>> print p.match('ababababab').span() (0, 10)
組用 "(" 和 ")" 來指定,而且獲得它們匹配文本的開始和結尾索引;這就能夠經過一個參數用 group()、start()、end() 和 span() 來進行檢索。組是從 0 開始計數的。組 0 老是存在;它就是整個 RE,因此 `MatchObject` 的方法都把組 0 做爲它們缺省的參數。稍後咱們將看到怎樣表達不能獲得它們所匹配文本的 span。
#!python >>> p = re.compile('(a)b') >>> m = p.match('ab') >>> m.group() 'ab' >>> m.group(0) 'ab'
小組是從左向右計數的,從1開始。組能夠被嵌套。計數的數值能夠經過從左到右計算打開的括號數來肯定。
#!python >>> p = re.compile('(a(b)c)d') >>> m = p.match('abcd') >>> m.group(0) 'abcd' >>> m.group(1) 'abc' >>> m.group(2) 'b'
group() 能夠一次輸入多個組號,在這種狀況下它將返回一個包含那些組所對應值的元組。
#!python >>> m.group(2,1,2) ('b', 'abc', 'b')
The groups() 方法返回一個包含全部小組字符串的元組,從 1 到 所含的小組號。
#!python >>> m.groups() ('abc', 'b')
模式中的逆向引用容許你指定先前捕獲組的內容,該組也必須在字符串當前位置被找到。舉個例子,若是組 1 的內容可以在當前位置找到的話,\1 就成功不然失敗。記住 Python 字符串也是用反斜槓加數據來容許字符串中包含任意字符的,因此當在 RE 中使用逆向引用時確保使用 raw 字符串。
例如,下面的 RE 在一個字符串中找到成雙的詞。
#!python >>> p = re.compile(r'(\b\w+)\s+\1') >>> p.search('Paris in the the spring').group() 'the the'
象這樣只是搜索一個字符串的逆向引用並不常見 -- 用這種方式重複數據的文本格式並很少見 -- 但你不久就能夠發現它們用在字符串替換上很是有用。
精心設計的 REs 也許會用不少組,既能夠捕獲感興趣的子串,又能夠分組和結構化 RE 自己。在複雜的 REs 裏,追蹤組號變得困難。有兩個功能能夠對這個問題有所幫助。它們也都使用正則表達式擴展的通用語法,所以咱們來看看第一個。
Perl 5 對標準正則表達式增長了幾個附加功能,Python 的 re 模塊也支持其中的大部分。選擇一個新的單按鍵元字符或一個以 "\" 開始的特殊序列來表示新的功能,而又不會使 Perl 正則表達式與標準正則表達式產生混亂是有難度的。若是你選擇 "&" 作爲新的元字符,舉個例子,老的表達式認爲 "&" 是一個正常的字符,而不會在使用 \& 或 [&] 時也不會轉義。
Perl 開發人員的解決方法是使用 (?...) 來作爲擴展語法。"?" 在括號後面會直接致使一個語法錯誤,由於 "?" 沒有任何字符能夠重複,所以它不會產生任何兼容問題。緊隨 "?" 以後的字符指出擴展的用途,所以 (?=foo)
Python 新增了一個擴展語法到 Perl 擴展語法中。若是在問號後的第一個字符是 "P",你就能夠知道它是針對 Python 的擴展。目前有兩個這樣的擴展: (?P<name>...) 定義一個命名組,(?P=name) 則是對命名組的逆向引用。若是 Perl 5 的將來版本使用不一樣的語法增長了相同的功能,那麼 re 模塊也將改變以支持新的語法,與此同時爲了兼容性的目的而繼續保持的 Python 專用語法。
如今咱們看一下普通的擴展語法,咱們回過頭來簡化在複雜 REs 中使用組運行的特性。由於組是從左到右編號的,並且一個複雜的表達式也許會使用許多組,它可使跟蹤當前組號變得困難,而修改如此複雜的 RE 是十分麻煩的。在開始時插入一個新組,你能夠改變它以後的每一個組號。
首先,有時你想用一個組去收集正則表達式的一部分,但又對組的內容不感興趣。你能夠用一個無捕獲組: (?:...) 來實現這項功能,這樣你能夠在括號中發送任何其餘正則表達式。
#!python >>> m = re.match("([abc])+", "abc") >>> m.groups() ('c',) >>> m = re.match("(?:[abc])+", "abc") >>> m.groups() ()
除了捕獲匹配組的內容以外,無捕獲組與捕獲組表現徹底同樣;你能夠在其中放置任何字符,能夠用重複元字符如 "*" 來重複它,能夠在其餘組(無捕獲組與捕獲組)中嵌套它。(?:...) 對於修改已有組尤爲有用,由於你能夠不用改變全部其餘組號的狀況下添加一個新組。捕獲組和無捕獲組在搜索效率方面也沒什麼不一樣,沒有哪個比另外一個更快。
其次,更重要和強大的是命名組;與用數字指定組不一樣的是,它能夠用名字來指定。
命令組的語法是 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"。
.*[.]([^b]..|.[^a].|..[^t])$
當你試着修補第一個解決方法而要求匹配下列狀況之一時表達式更亂了:擴展名的第一個字符不是 "b"; 第二個字符不是 "a";或第三個字符不是 "t"。這樣能夠接受 "foo.bar" 而拒絕 "autoexec.bat",但這要求只能是三個字符的擴展名而不接受兩個字符的擴展名如 "sendmail.cf"。咱們將在努力修補它時再次把該模式變得複雜。
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
在第三次嘗試中,第二和第三個字母都變成可選,爲的是容許匹配比三個字符更短的擴展名,如 "sendmail.cf"。
該模式如今變得很是複雜,這使它很難讀懂。更糟的是,若是問題變化了,你想擴展名不是 "bat" 和 "exe",該模式甚至會變得更復雜和混亂。
前向否認把全部這些裁剪成:
.*[.](?!bat$).*$
前向的意思:若是表達式 bat 在這裏沒有匹配,嘗試模式的其他部分;若是 bat$ 匹配,整個模式將失敗。後面的 $ 被要求是爲了確保象 "sample.batch" 這樣擴展名以 "bat" 開頭的會被容許。
將另外一個文件擴展名排除在外如今也容易;簡單地將其作爲可選項放在界定符中。下面的這個模式將以 "bat" 或 "exe" 結尾的文件名排除在外。
.*[.](?!bat$|exe$).*$
補充:
注意:這裏介紹的平衡組語法是由.Net Framework支持的;其它語言/庫不必定支持這種功能,或者支持此功能但須要使用不一樣的語法。
有時咱們須要匹配像( 100 * ( 50 + 15 ) )這樣的可嵌套的層次性結構,這時簡單地使用\(.+\)則只會匹配到最左邊的左括號和最右邊的右括號之間的內容(這裏咱們討論的是貪婪模式,懶惰模式也有下面的問題)。假如原來的字符串裏的左括號和右括號出現的次數不相等,好比( 5 / ( 3 + 2 ) ) ),那咱們的匹配結果裏二者的個數也不會相等。有沒有辦法在這樣的字符串裏匹配到最長的,配對的括號之間的內容呢?
爲了不(和\(把你的大腦完全搞糊塗,咱們仍是用尖括號代替圓括號吧。如今咱們的問題變成了如何把xx <aa <bbb> <bbb> aa> yy這樣的字符串裏,最長的配對的尖括號內的內容捕獲出來?
這裏須要用到如下的語法構造:
若是你不是一個程序員(或者你是一個對堆棧的概念不熟的程序員),你就這樣理解上面的三種語法吧:第一個就是在黑板上寫一個 "group",第二個就是從黑板上擦掉一個"group",第三個就是看黑板上寫的還有沒有"group",若是有就繼續匹配yes部分,不然就匹配 no部分。
咱們須要作的是每碰到了左括號,就在黑板上寫一個"group",每碰到一個右括號,就擦掉一個,到了最後就看看黑板上還有沒有--若是有那就證實左括號比右括號多,那匹配就應該失敗。
< #最外層的左括號 [^<>]* #最外層的左括號後面的不是括號的內容 ( ( (?'Open'<) #碰到了左括號,在黑板上寫一個"Open" [^<>]* #匹配左括號後面的不是括號的內容 )+ ( (?'-Open'>) #碰到了右括號,擦掉一個"Open" [^<>]* #匹配右括號後面不是括號的內容 )+ )* (?(Open)(?!)) #在遇到最外層的右括號前面,判斷黑板上還有沒有沒擦掉的"Open";若是還有,則匹配失敗 > #最外層的右括號
平衡組的一個最多見的應用就是匹配HTML,下面這個例子能夠匹配嵌套的<div>標籤:<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>.
(?<x>-<y>exp) 平衡組
(?im-nsx:exp) 在子表達式exp中改變處理選項
(?im-nsx) 爲表達式後面的部分改變處理選項
(?(exp)yes|no) 把exp看成零寬正向先行斷言,若是在這個位置能匹配,使用yes做爲此組的表達式;不然使用no
(?(exp)yes) 同上,只是使用空表達式做爲no
(?(name)yes|no) 若是命名爲name的組捕獲到了內容,使用yes做爲表達式;不然使用no
(?(name)yes) 同上,只是使用空表達式做爲no
到目前爲止,咱們簡單地搜索了一個靜態字符串。正則表達式一般也用不一樣的方式,經過下面的 `RegexObject` 方法,來修改字符串。
方法/屬性 | 做用 |
split() | 將字符串在 RE 匹配的地方分片並生成一個列表, |
sub() | 找到 RE 匹配的全部子串,並將其用一個不一樣的字符串替換 |
subn() | 與 sub() 相同,但返回新的字符串和替換次數 |
`RegexObject` 的 split() 方法在 RE 匹配的地方將字符串分片,將返回列表。它同字符串的 split() 方法類似但提供更多的定界符;split()只支持空白符和固定字符串。就象你預料的那樣,也有一個模塊級的 re.split() 函數。
split(string [, maxsplit = 0])
經過正則表達式將字符串分片。若是捕獲括號在 RE 中使用,那麼它們的內容也會做爲結果列表的一部分返回。若是 maxsplit 非零,那麼最多隻能分出 maxsplit 個分片。
你能夠經過設置 maxsplit 值來限制分片數。當 maxsplit 非零時,最多隻能有 maxsplit 個分片,字符串的其他部分被作爲列表的最後部分返回。在下面的例子中,定界符能夠是非數字字母字符的任意序列。
#!python >>> p = re.compile(r'\W+') >>> p.split('This is a test, short and sweet, of split().') ['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', ''] >>> p.split('This is a test, short and sweet, of split().', 3) ['This', 'is', 'a', 'test, short and sweet, of split().']
有時,你不只對定界符之間的文本感興趣,也須要知道定界符是什麼。若是捕獲括號在 RE 中使用,那麼它們的值也會看成列表的一部分返回。比較下面的調用:
#!python >>> p = re.compile(r'\W+') >>> p2 = re.compile(r'(\W+)') >>> p.split('This... is a test.') ['This', 'is', 'a', 'test', ''] >>> p2.split('This... is a test.') ['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']
模塊級函數 re.split() 將 RE 做爲第一個參數,其餘同樣。
#!python >>> re.split('[\W]+', 'Words, words, words.') ['Words', 'words', 'words', ''] >>> re.split('([\W]+)', 'Words, words, words.') ['Words', ', ', 'words', ', ', 'words', '.', ''] >>> re.split('[\W]+', 'Words, words, words.', 1) ['Words', 'words, words.']
其餘常見的用途就是找到全部模式匹配的字符串並用不一樣的字符串來替換它們。sub() 方法提供一個替換值,能夠是字符串或一個函數,和一個要被處理的字符串。
sub(replacement, string[, count = 0])
返回的字符串是在字符串中用 RE 最左邊不重複的匹配來替換。若是模式沒有發現,字符將被沒有改變地返回。
可選參數 count 是模式匹配後替換的最大次數;count 必須是非負整數。缺省值是 0 表示替換全部的匹配。
這裏有個使用 sub() 方法的簡單例子。它用單詞 "colour" 替換顏色名。
#!python >>> p = re.compile( '(blue|white|red)') >>> p.sub( 'colour', 'blue socks and red shoes') 'colour socks and colour shoes' >>> p.sub( 'colour', 'blue socks and red shoes', count=1) 'colour socks and red shoes'
subn() 方法做用同樣,但返回的是包含新字符串和替換執行次數的兩元組。
#!python >>> p = re.compile( '(blue|white|red)') >>> p.subn( 'colour', 'blue socks and red shoes') ('colour socks and colour shoes', 2) >>> p.subn( 'colour', 'no colours at all') ('no colours at all', 0)
空匹配只有在它們沒有緊挨着前一個匹配時纔會被替換掉。
#!python >>> p = re.compile('x*') >>> p.sub('-', 'abxd') '-a-b-d-'
若是替換的是一個字符串,任何在其中的反斜槓都會被處理。"\n" 將會被轉換成一個換行符,"\r"轉換成回車等等。未知的轉義如 "\j" 則保持原樣。逆向引用,如 "\6",被 RE 中相應的組匹配而被子串替換。這使你能夠在替換後的字符串中插入原始文本的一部分。
這個例子匹配被 "{" 和 "}" 括起來的單詞 "section",並將 "section" 替換成 "subsection"。
#!python >>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE) >>> p.sub(r'subsection{\1}','section{First} section{second}') 'subsection{First} subsection{second}'
還能夠指定用 (?P<name>...) 語法定義的命名組。"\g<name>" 將經過組名 "name" 用子串來匹配,而且 "\g<number>" 使用相應的組號。因此 "\g<2>" 等於 "\2",但能在替換字符串裏含義不清,如 "\g<2>0"。("\20" 被解釋成對組 20 的引用,而不是對後面跟着一個字母 "0" 的組 2 的引用。)
#!python >>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE) >>> p.sub(r'subsection{\1}','section{First}') 'subsection{First}' >>> p.sub(r'subsection{\g<1>}','section{First}') 'subsection{First}' >>> p.sub(r'subsection{\g<name>}','section{First}') 'subsection{First}'
替換也能夠是一個甚至給你更多控制的函數。若是替換是個函數,該函數將會被模式中每個不重複的匹配所調用。在每次調用時,函數會被傳入一個 `MatchObject` 的對象最爲參數,所以能夠用這個對象去計算出替換字符串並返回它。
在下面的例子裏,替換函數將十進制翻譯成十六進制:
#!python >>> def hexrepl( match ): ... "Return the hex string for a decimal number" ... value = int( match.group() ) ... return hex(value) ... >>> p = re.compile(r'\d+') >>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.') 'Call 0xffd2 for printing, 0xc000 for user code.'
當使用模塊級的 re.sub() 函數時,模式做爲第一個參數。模式也許是一個字符串或一個 `RegexObject`;若是你須要指定正則表達式標誌,你必需要麼使用 `RegexObject` 作第一個參數,或用使用模式內嵌修正器,如 sub("(?i)b+", "x", "bbbb BBBB") returns 'x x'。
正則表達式對一些應用程序來講是一個強大的工具,但在有些時候它並不直觀並且有時它們不按你指望的運行。本節將指出一些最容易犯的常見錯誤。
有時使用 re 模塊是個錯誤。若是你匹配一個固定的字符串或單個的字符類,而且你沒有使用 re 的任何象 IGNORECASE 標誌的功能,那麼就沒有必要使用正則表達式了。字符串有一些方法是對固定字符串進行操做的,它們一般快不少,由於它們都是一個個通過優化的 C 小循環,用以代替大的、更具通用性的正則表達式引擎。
舉個 用一個固定字符串替換另外一個 的例子,如:你能夠把 "deed" 替換成 "word"。re.sub() 彷佛正是勝任這個工做的函數,但仍是考慮考慮 replace() 方法吧。注意 replace() 也能夠在單詞裏面進行替換,能夠把 "swordfish" 變成 "sdeedfish"。不過 RE 也是能夠作到的。(爲了不替換單詞的一部分,模式將寫成 \bword\b,這是爲了要求 "word" 兩邊有一個單詞邊界。這是個超出 replace 能力的工做)。
另外一個常見任務是從一個字符串中刪除單個字符或用另外一個字符來替代它。你也許能夠用 re.sub('\n',' ', s) 這樣來實現,但 translate() 可以實現這兩個任務,並且比任何正則表達式操做起來更快。(translate 須要配合 string.maketrans 使用。例如:import string 後 'a1b3'.translate(string.maketrans('ab', 'cd')) )
總之,在使用 re 模塊以前,先考慮一下你的問題是否能夠用更快、更簡單的字符串方法來解決。
match() 函數只檢查 RE 是否在字符串開始處匹配,而 search() 則是掃描整個字符串。記住這一區別是重要的。記住,match() 只報告一次成功的匹配,它將從 0 處開始;若是匹配不是從 0 開始的,match() 將不會報告它。
#!python >>> print re.match('super', 'superstition').span() (0, 5) >>> print re.match('super', 'insuperable') None
另外一方面,search() 將掃描整個字符串,並報告它找到的第一個匹配。
#!python >>> print re.search('super', 'superstition').span() (0, 5) >>> print re.search('super', 'insuperable').span() (2, 7)
有時你可能傾向於使用 re.match(),只在RE的前面部分添加 .* 。請儘可能不要這麼作,最好採用 re.search() 代替之。正則表達式編譯器會對 REs 作一些分析以即可以在查找匹配時提升處理速度。一個那樣的分析機會指出匹配的第一個字符是什麼;舉個例子,模式 Crow 必須從 "C" 開始匹配。分析機可讓引擎快速掃描字符串以找到開始字符,並只在 "C" 被發現後纔開始所有匹配。
添加 .* 會使這個優化失敗,這就要掃描到字符串尾部,而後回溯以找到 RE 剩餘部分的匹配。使用 re.search() 代替。
當重複一個正則表達式時,如用 a*,操做結果是儘量多地匹配模式。當你試着匹配一對對稱的定界符,如 HTML 標誌中的尖括號時這個事實常常困擾你。匹配單個 HTML 標誌的模式不能正常工做,由於 .* 的本質是「貪婪」的
#!python >>> s = '<html><head><title>Title</title>' >>> len(s) 32 >>> print re.match('<.*>', s).span() (0, 32) >>> print re.match('<.*>', s).group() <html><head><title>Title</title>
RE 匹配 在 "<html>" 中的 "<",.* 消耗掉子符串的剩餘部分。在 RE 中保持更多的左,雖然 > 不能匹配在字符串結尾,所以正則表達式必須一個字符一個字符地回溯,直到它找到 > 的匹配。最終的匹配從 "<html" 中的 "<" 到 "</title>" 中的 ">",這並非你所想要的結果。
在這種狀況下,解決方案是使用不貪婪的限定符 *?、+?、?? 或 {m,n}?,儘量匹配小的文本。在上面的例子裏, ">" 在第一個 "<" 以後被當即嘗試,當它失敗時,引擎一次增長一個字符,並在每步重試 ">"。這個處理將獲得正確的結果:
#!python >>> print re.match('<.*?>', s).group() <html>
注意用正則表達式分析 HTML 或 XML 是痛苦的。變化混亂的模式將處理常見狀況,但 HTML 和 XML 則是明顯會打破正則表達式的特殊狀況;當你編寫一個正則表達式去處理全部可能的狀況時,模式將變得很是複雜。象這樣的任務用 HTML 或 XML 解析器。
粗體文字粗體文字'
如今你可能注意到正則表達式的表示是十分緊湊,但它們很是很差讀。中度複雜的 REs 能夠變成反斜槓、圓括號和元字符的長長集合,以至於使它們很難讀懂。
在這些 REs 中,當編譯正則表達式時指定 re.VERBOSE 標誌是有幫助的,由於它容許你能夠編輯正則表達式的格式使之更清楚。
re.VERBOSE 標誌有這麼幾個做用。在正則表達式中不在字符類中的空白符被忽略。這就意味着象 dog | cat 這樣的表達式和可讀性差的 dog|cat 相同,但 [a b] 將匹配字符 "a"、"b" 或 空格。另外,你也能夠把註釋放到 RE 中;註釋是從 "#" 到下一行。當使用三引號字符串時,可使 REs 格式更加乾淨:
#!python pat = re.compile(r""" \s* # Skip leading whitespace (?P<header>[^:]+) # Header name \s* : # Whitespace, and a colon (?P<value>.*?) # The header's value -- *? used to # lose the following trailing whitespace \s*$ # Trailing whitespace to end-of-line """, re.VERBOSE)
這個要難讀得多:
#!python pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")