1. 程式人生 > >python | 史上最全的正則表示式

python | 史上最全的正則表示式

1 Python 正則式的基本用法 --原文地址:http://blog.csdn.net/whycadi/archive/2008/01/02/2011046.aspx

Python 的正則表示式的模組是 ‘re’, 它的基本語法規則就是指定一個字元序列,比如你要在一個字串 s=’123abc456’ 中查詢字串 ’abc’, 只要這樣寫:

>>> import re

>>> s='123abc456eabc789'

>>> re.findall(r’abc’,s)

結果就是:

['abc', 'abc']

這裡用到的函式 ”findall(rule , target [,flag] )” 是個比較直觀的函式,就是在目標字串中查詢符合規則的字串。第一個引數是規則,第二個引數是目標字串,後面還可以跟一個規則選項(選項功能將在 compile 函式的說明中詳細說明)。返回結果結果是一個列表,

 中間存放的是符合規則的字串。如果沒有符合規則的字串被找到,就返回一個 列表。

為什麼要用 r’ ..‘ 字串( raw 字串)? 由於正則式的規則也是由一個字串定義的,而在正則式中大量使用轉義字元 ’/’ ,如果不用 raw 字串,則在需要寫一個 ’/’ 的地方,你必須得寫成 ’//’, 那麼在要從目標字串中匹配一個 ’/’ 的時候,你就得寫上 4 個 ’/’ 成為 ’////’ !這當然很麻煩,也不直觀,所以一般都使用 r’’ 來定義規則字串。當然,某些情況下,可能不用 raw 字串比較好。

以上是個最簡單的例子。當然實際中這麼簡單的用法幾乎沒有意義。為了實現複雜的規則查詢, re 規定了若干語法規則。它們分為這麼幾類:

功能字元 :     ‘.’ ‘*’ ‘+’ ‘|’ ‘?’ ‘^’ ‘$’ ‘/’ 等,它們有特殊的功能含義。特別是 ’/’ 字元,它是轉義引導符號,跟在它後面的字元一般有特殊的含義。

規則分界符: ‘[‘ ‘]’ ‘ ( ’ ‘ ) ’ ‘{‘ ‘}’ 等,也就是幾種括號了。

預定義轉義字符集: “/d”  “/w” “/s” 等等,它們是以字元 ’/’ 開頭,後面接一個特定字元的形式,用來指示一個預定義好的含義。

其它特殊功能字元: ’#’ ‘!’ ‘:’ ‘-‘ 等,它們只在特定的情況下表示特殊的含義,比如 (?# …) 就表示一個註釋,裡面的內容會被忽略。

下面來一個一個的說明這些規則的含義,不過說明的順序並不是按照上面的順序來的,而是我認為由淺入深,由基本到複雜的順序來編排的。同時為了直觀,在說明的過程中儘量多舉些例子以方便理解。

1.1 基本規則

‘[‘  ‘]’ 字元集合設定符

首先說明一下字元集合設定的方法。由一對方括號括起來的字元,表明一個字元集合,能夠匹配包含在其中的任意一個字元。比如 [abc123] ,表明字元 ’a’ ‘b’ ‘c’ ‘1’ ‘2’ ‘3’ 都符合它的要求。可以被匹配。

在 ’[‘ ‘]’ 中還可以通過 ’-‘ 減號來指定一個字元集合的範圍,比如可以用 [a-zA-Z] 來指定所以英文字母的大小寫,因為英文字母是按照從小到大的順序來排的。你不可以把大小的順序顛倒了,比如寫成 [z-a] 就不對了。

如果在 ’[‘ ‘]’ 裡面的開頭寫一個 ‘^’ 號,則表示取非,即在括號裡的字元都不匹配。如 [^a-zA-Z] 表明不匹配所有英文字母。但是如果 ‘^’ 不在開頭,則它就不再是表示取非,而表示其本身,如 [a-z^A-Z] 表明匹配所有的英文字母和字元 ’^’ 。

‘|’    或規則

將兩個規則並列起來,以‘ | ’連線,表示只要滿足其中之一就可以匹配。比如

[a-zA-Z]|[0-9] 表示滿足數字或字母就可以匹配,這個規則等價於 [a-zA-Z0-9]

注意 :關於 ’|’ 要注意兩點:

第一,            它在 ’[‘ ‘]’ 之中不再表示或,而表示他本身的字元。如果要在 ’[‘ ‘]’ 外面表示一個 ’|’ 字元,必須用反斜槓引導,即 ’/|’ ;

第二,            它的有效範圍是它兩邊的整條規則,比如‘ dog|cat’ 匹配的是‘ dog’ 和 ’cat’ ,而不是 ’g’ 和 ’c’ 。如果想限定它的有效範圍,必需使用一個無捕獲組 ‘(?: )’ 包起來。比如要匹配 ‘ I have a dog’ 或 ’I have a cat’ ,需要寫成 r’I have a (?:dog|cat)’ ,而不能寫成 r’I have a dog|cat’

>>> s = ‘I have a dog , I have a cat’

>>> re.findall( r’I have a (?:dog|cat)’ , s )

['I have a dog', 'I have a cat']                # 正如我們所要的

下面再看看不用無捕獲組會是什麼後果:

>>> re.findall( r’I have a dog|cat’ , s )

['I have a dog', 'cat']                                   # 它將 ’I have a dog’ 和 ’cat’ 當成兩個規則了

至於無捕獲組的使用,後面將仔細說明。這裡先跳過。

‘.’    匹配所有字元

匹配除換行符 ’/n’ 外的所有字元。如果使用了 ’S’ 選項,匹配包括 ’/n’ 的所有字元。

       例:

       >>> s=’123 /n456 /n789’

       >>> findall(r‘.+’,s)

       ['123', '456', '789']

       >>> re.findall(r‘.+’ , s , re.S)

       ['123/n456/n789']

‘^’ 和 ’$’ 匹配字串開頭和結尾

注意 ’^’ 不能在‘ [ ] ’中,否則含意就發生變化,具體請看上面的 ’[‘ ‘]’ 說明。 在多行模式下,它們可以匹配每一行的行首和行尾。具體請看後面 compile 函式說明的 ’M’ 選項部分

‘/d’ 匹配數字

這是一個以 ’/’ 開頭的轉義字元, ’/d’ 表示匹配一個數字,即等價於 [0-9]

‘/D’ 匹配非數字

這個是上面的反集,即匹配一個非數字的字元,等價於 [^0-9] 。注意它們的大小寫。下面我們還將看到 Python 的正則規則中很多轉義字元的大小寫形式,代表互補的關係。這樣很好記。

‘/w’ 匹配字母和數字

匹配所有的英文字母和數字,即等價於 [a-zA-Z0-9] 。

‘/W’ 匹配非英文字母和數字

即 ’/w’ 的補集,等價於 [^a-zA-Z0-9] 。

‘/s’ 匹配間隔符

即匹配空格符、製表符、回車符等表示分隔意義的字元,它等價於 [ /t/r/n/f/v] 。(注意最前面有個空格 )

‘/S’ 匹配非間隔符

即間隔符的補集,等價於 [^ /t/r/n/f/v]

‘/A’ 匹配字串開頭

匹配字串的開頭。它和 ’^’ 的區別是, ’/A’ 只匹配整個字串的開頭,即使在 ’M’ 模式下,它也不會匹配其它行的很首。

‘/Z’ 匹配字串結尾

匹配字串的結尾。它和 ’$’ 的區別是, ’/Z’ 只匹配整個字串的結尾,即使在 ’M’ 模式下,它也不會匹配其它各行的行尾。

例:

>>> s= '12 34/n56 78/n90'

>>> re.findall( r'^/d+' , s , re.M )          # 匹配位於行首的數字

['12', '56', '90']

>>> re.findall( r’/A/d+’, s , re.M )        # 匹配位於字串開頭的數字

['12']

>>> re.findall( r'/d+$' , s , re.M )          # 匹配位於行尾的數字

['34', '78', '90']

>>> re.findall( r’/d+/Z’ , s , re.M )        # 匹配位於字串尾的數字

['90']

‘/b’ 匹配單詞邊界

它匹配一個單詞的邊界,比如空格等,不過它是一個‘ 0 ’長度字元,它匹配完的字串不會包括那個分界的字元。而如果用 ’/s’ 來匹配的話,則匹配出的字串中會包含那個分界符。

例:

>>> s =  'abc abcde bc bcd'

>>> re.findall( r’/bbc/b’ , s )         # 匹配一個單獨的單詞 ‘bc’ ,而當它是其它單詞的一部分的時候不匹配

['bc']                                           #只找到了那個單獨的 ’bc’

>>> re.findall( r’/sbc/s’ , s )          #匹配一個單獨的單詞 ‘bc’

[' bc ']                                         # 只找到那個單獨的 ’bc’ ,不過注意前後有兩個空格,可能有點看不清楚

‘/B’ 匹配非邊界

和 ’/b’ 相反,它只匹配非邊界的字元。它同樣是個 0 長度字元。

接上例:

>>> re.findall( r’/Bbc/w+’ , s )     # 匹配包含 ’bc’ 但不以 ’bc’ 為開頭的單詞

['bcde']                                       # 成功匹配了 ’abcde’ 中的 ’bcde’ ,而沒有匹配 ’bcd’

‘(?:)’ 無捕獲組

當你要將一部分規則作為一個整體對它進行某些操作,比如指定其重複次數時,你需要將這部分規則用 ’(?:’ ‘)’ 把它包圍起來,而不能僅僅只用一對括號,那樣將得到絕對出人意料的結果。

例:匹配字串中重複的 ’ab’

>>> s=’ababab abbabb aabaab’

>>> re.findall( r’/b(?:ab)+/b’ , s )

['ababab']

如果僅使用一對括號,看看會是什麼結果:

>>> re.findall( r’/b(ab)+/b’ , s )

['ab']

這是因為如果只使用一對括號,那麼這就成為了一個組 (group) 。組的使用比較複雜,將在後面詳細講解。

‘(?# )’ 註釋

Python 允許你在正則表示式中寫入註釋,在 ’(?#’ ‘)’ 之間的內容將被忽略。

(?iLmsux) 編譯選項指定

Python 的正則式可以指定一些選項,這個選項可以寫在 findall 或 compile 的引數中,也可以寫在正則式裡,成為正則式的一部分。這在某些情況下會便利一些。具體的選項含義請看後面的 compile 函式的說明。

此處編譯選項 ’i’ 等價於 IGNORECASE ,L 等價於 LOCAL ,m 等價於 MULTILINE , s 等價於 DOTALL , u 等價於 UNICODE , x 等價於 VERBOSE 。

請注意它們的大小寫。在使用時可以只指定一部分,比如只指定忽略大小寫,可寫為 ‘(?i)’ ,要同時忽略大小寫並使用多行模式,可以寫為 ‘(?im)’ 。

另外要注意選項的有效範圍是整條規則,即寫在規則的任何地方,選項都會對全部整條正則式有效。

1.2 重複

正則式需要匹配不定長的字串,那就一定需要表示重複的指示符。 Python 的正則式表示重複的功能很豐富靈活。重複規則的一般的形式是在一條字元規則後面緊跟一個表示重複次數的規則,已表明需要重複前面的規則一定的次數。重複規則有:

‘*’   0 或多次匹配

表示匹配前面的規則 0 次或多次。

‘+’   1 次或多次匹配

表示匹配前面的規則至少 1 次,可以多次匹配

例:匹配以下字串中的前一部分是字母,後一部分是數字或沒有的變數名字

>>> s = ‘ aaa bbb111 cc22cc 33dd ‘

>>> re.findall( r’/b[a-z]+/d*/b’ , s )             # 必須至少 1 個字母開頭,以連續數字結尾或沒有數字

['aaa', 'bbb111']

注意上例中規則前後加了表示單詞邊界的 ’/b’ 指示符,如果不加的話結果就會變成:

>>> re.findall( r’[a-z]+/d*’ , s )

['aaa', 'bbb111', 'cc22', 'cc', 'dd']    # 把單詞給拆開了

大多數情況下這不是我們期望的結果。

‘?’   0 或 1 次匹配

只匹配前面的規則 0 次或 1 次。

例,匹配一個數字,這個數字可以是一個整數,也可以是一個科學計數法記錄的數字,比如 123 和 10e3 都是正確的數字。

>>> s = ‘ 123 10e3 20e4e4 30ee5 ‘

>>> re.findall( r’ /b/d+[eE]?/d*/b’ , s )

['123', '10e3']

它正確匹配了 123 和 10e3, 正是我們期望的。注意前後的 ’/b’ 的使用,否則將得到不期望的結果。

1.2.1 精確匹配和最小匹配

Python 正則式還可以精確指定匹配的次數。指定的方式是

‘{m}’      精確匹配 m 次

‘{m,n}’   匹配最少 m 次,最多 n 次。 (n>m)

如果你只想指定一個最少次數或只指定一個最多次數,你可以把另外一個引數空起來。比如你想指定最少 3 次,可以寫成 {3,} (注意那個逗號),同樣如果只想指定最大為 5 次,可以寫成 { , 5} ,也可以寫成 {0,5} 。

例 尋找下面字串中

a : 3 位數

b: 2 位數到 4 位數

c: 5 位數以上的數

d: 4 位數以下的數

>>> s= ‘ 1 22 333 4444 55555 666666 ‘

>>> re.findall( r’/b/d{3}/b’ , s )            # a : 3 位數

['333']

>>> re.findall( r’/b/d{2,4}/b’ , s )         # b: 2 位數到 4 位數

['22', '333', '4444']

>>> re.findall( r’/b/d{5,}/b’, s )           # c: 5 位數以上的數

['55555', '666666']

>>> re.findall( r’/b/d{1,4}/b’ , s )         # 4 位數以下的數

['1', '22', '333', '4444']

‘*?’ ‘+?’ ‘??’ 最小匹配

‘*’ ‘+’ ‘?’ 通常都是儘可能多的匹配字元。有時候我們希望它儘可能少的匹配。比如一個 c 語言的註釋 ‘/* part 1 */ /* part 2 */’ ,如果使用最大規則:

>>> s =r ‘/* part 1 */ code /* part 2 */’

>>> re.findall( r’//*.*/*/’ , s )

[‘/* part 1 */ code /* part 2 */’]

結果把整個字串都包括進去了。如果把規則改寫成

>>> re.findall( r’//*.*?/*/’ , s )                    # 在 * 後面加上 ? ,表示儘可能少的匹配

['/* part 1 */', '/* part 2 */']

結果正確的匹配出了註釋裡的內容

1.3   前向界定與後向界定

有時候需要匹配一個跟在特定內容後面的或者在特定內容前面的字串, Python 提供一個簡便的前向界定和後向界定功能,或者叫前導指定和跟從指定功能。它們是:

‘(?<=…)’ 前向界定

括號中 ’…’ 代表你希望匹配的字串的前面應該出現的字串。

‘(?=…)’  後向界定

括號中的 ’…’ 代表你希望匹配的字串後面應該出現的字串。

例: 你希望找出 c 語言的註釋中的內容,它們是包含在 ’/*’ 和 ’*/’ 之間,不過你並不希望匹配的結果把 ’/*’ 和 ’*/’ 也包括進來,那麼你可以這樣用:

>>> s=r’/* comment 1 */  code  /* comment 2 */’

>>> re.findall( r’(?<=//*).+?(?=/*/)’ , s )

[' comment 1 ', ' comment 2 ']

注意這裡我們仍然使用了最小匹配,以避免把整個字串給匹配進去了。

要注意的是,前向界定括號中的表示式必須是常值,也即你不可以在前向界定的括號裡寫正則式。比如你如果在下面的字串中想找到被字母夾在中間的數字,你不可以用前向界定:

例:

>>> s = ‘aaa111aaa , bbb222 , 333ccc ‘

>>> re.findall( r’(?<=[a-z]+)/d+(?=[a-z]+)' , s )          # 錯誤的用法

它會給出一個錯誤資訊:

error: look-behind requires fixed-width pattern

不過如果你只要找出後面接著有字母的數字,你可以在後向界定寫正則式:

>>> re.findall( r’/d+(?=[a-z]+)’, s )

['111', '333']

如果你一定要匹配包夾在字母中間的數字,你可以使用組( group )的方式

>>> re.findall (r'[a-z]+(/d+)[a-z]+' , s )

['111']

組的使用將在後面詳細講解。

除了前向界定前向界定和後向界定外,還有前向非界定和後向非界定,它的寫法為:

‘(?<!...)’ 前向非界定

只有當你希望的字串前面不是’…’ 的內容時才匹配

‘(?!...)’ 後向非界定

只有當你希望的字串後面不跟著 ’…’ 內容時才匹配。

接上例,希望匹配後面不跟著字母的數字

>>> re.findall( r’/d+(?!/w+)’ , s )

['222']

注意這裡我們使用了 /w 而不是像上面那樣用 [a-z] ,因為如果這樣寫的話,結果會是:

>>> re.findall( r’/d+(?![a-z]+)’ , s )

['11', '222', '33']

這和我們期望的似乎有點不一樣。它的原因,是因為 ’111’ 和 ’222’ 中的前兩個數字也是滿足這個要求的。因此可看出,正則式的使用還是要相當小心的,因為我開始就是這樣寫的,看到結果後才明白過來。不過 Python 試驗起來很方便,這也是指令碼語言的一大優點,可以一步一步的試驗,快速得到結果,而不用經過煩瑣的編譯、連結過程。也因此學習 Python 就要多試,跌跌撞撞的走過來,雖然曲折,卻也很有樂趣。

1.4 組的基本知識

上面我們已經看過了 Python 的正則式的很多基本用法。不過如果僅僅是上面那些規則的話,還是有很多情況下會非常麻煩,比如上面在講前向界定和後向界定時,取夾在字母中間的數字的例子。用前面講過的規則都很難達到目的,但是用了組以後就很簡單了。

‘(‘’)’       無命名組

最基本的組是由一對圓括號括起來的正則式。比如上面匹配包夾在字母中間的數字的例子中使用的 (/d+) ,我們再回顧一下這個例子:

>>> s = ‘aaa111aaa , bbb222 , 333ccc ‘

>>> re.findall (r'[a-z]+(/d+)[a-z]+' , s )

['111']

可以看到 findall 函式只返回了包含在 ’()’ 中的內容,而雖然前面和後面的內容都匹配成功了,卻並不包含在結果中。

除了最基本的形式外,我們還可以給組起個名字,它的形式是

‘(?P<name>…)’ 命名組

‘(?P’ 代表這是一個 Python 的語法擴充套件 ’<…>’ 裡面是你給這個組起的名字,比如你可以給一個全部由數字組成的組叫做 ’num’ ,它的形式就是 ’(?P<num>/d+)’ 。起了名字之後,我們就可以在後面的正則式中通過名字呼叫這個組,它的形式是

‘(?P=name)’ 呼叫已匹配的命名組

要注意,再次呼叫的這個組是已被匹配的組,也就是說它裡面的內容是和前面命名組裡的內容是一樣的。

我們可以看更多的例子:請注意下面這個字串各子串的特點。

>>> s='aaa111aaa,bbb222,333ccc,444ddd444,555eee666,fff777ggg'

我們看看下面的正則式會返回什麼樣的結果:

>>> re.findall( r'([a-z]+)/d+([a-z]+)' , s )             # 找出中間夾有數字的字母

[('aaa', 'aaa'), ('fff', 'ggg')]

>>> re.findall( r '(?P<g1>[a-z]+)/d+(?P=g1)' , s ) # 找出被中間夾有數字的前後同樣的字母

['aaa']

>>> re.findall( r'[a-z]+(/d+)([a-z]+)' , s )             # 找出前面有字母引導,中間是數字,後面是字母的字串中的中間的數字和後面的字母

[('111', 'aaa'), ('777', 'ggg')]

我們可以通過命名組的名字在後面呼叫已匹配的命名組,不過名字也不是必需的。

‘/number’             通過序號呼叫已匹配的組

正則式中的每個組都有一個序號,序號是按組從左到右,從 1 開始的數字,你可以通過下面的形式來呼叫已匹配的組

比如上面找出被中間夾有數字的前後同樣的字母的例子,也可以寫成:

>>> re.findall( r’([a-z]+)/d+/1’ , s )

['aaa']

結果是一樣的。

我們再看一個例子

>>> s='111aaa222aaa111 , 333bbb444bb33'

>>> re.findall( r'(/d+)([a-z]+)(/d+)(/2)(/1)' , s )           # 找出完全對稱的 數字-字母-數字-字母-數字 中的數字和字母

[('111', 'aaa', '222', 'aaa', '111')]

Python2.4 以後的 re 模組,還加入了一個新的條件匹配功能

‘(?( id/name )yes-pattern|no-pattern)’ 判斷指定組是否已匹配,執行相應的規則

這個規則的含義是,如果 id/name 指定的組在前面匹配成功了,則執行 yes-pattern 的正則式,否則執行 no-pattern 的正則式。

舉個例子,比如要匹配一些形如 [email protected] 的郵箱地址,不過有的寫成 < [email protected] > 即用一對 <> 括起來,有點則沒有,要匹配這兩種情況,可以這樣寫

>>> s='<[email protected]>  [email protected]'

>>> re.findall( r'(<)?/s*(/[email protected]/w+)/s*(?(1)>)' , s )

[('<', '[email protected]'), ('', '[email protected]')]

不過如果目標字串如下

>>> s='<[email protected]>  [email protected] <[email protected]   [email protected]>  < [email protected] '

而你想得到要麼由一對 <> 包圍起來的一個郵件地址,要麼得到一個沒有被 <> 包圍起來的地址,但不想得到一對 <> 中間包圍的多個地址或不完整的 <> 中的地址,那麼使用這個式子並不能得到你想要的結果

>>> re.findall( r'(<)?/s*(/[email protected]/w+)/s*(?(1)>)' , s )

[('<', '[email protected]'), ('', '[email protected]'), ('', '[email protected]'), ('', '[email protected]'), ('', '[email protected]')]

它仍然找到了所有的郵件地址。

想要實現這個功能,單純的使用 findall 有點吃力,需要使用其它的一些函式,比如 match 或 search 函式,再配合一些控制功能。這部分的內容將在下面詳細講解。

小結:以上基本上講述了 Python 正則式的語法規則。雖然大部分語法規則看上去都很簡單,可是稍不注意,仍然會得到與期望大相徑庭的結果,所以要寫好正則式,需要仔細的體會正則式規則的含義後不同規則之間細微的差別。

詳細的瞭解了規則後,再配合後面就要介紹的功能函式,就能最大的發揮正則式的威力了。

2 re 模組的基本函式

在上面的說明中,我們已經對 re 模組的基本函式 ‘findall’ 很熟悉了。當然如果光有 findall 的話,很多功能是不能實現的。下面開始介紹一下 re 模組其它的常用基本函式。靈活搭配使用這些函式,才能充分發揮 Python 正則式的強大功能。

首先還是說下老熟人 findall 函式吧

findall(rule , target [,flag] )

在目標字串中查詢符合規則的字串。

第一個引數是規則,第二個引數是目標字串,後面還可以跟一個規則選項(選項功能將在 compile 函式的說明中詳細說明)。

返回結果結果是一個列表, 中間存放的是符合規則的字串。如果沒有符合規則的字串被找到,就返回一個 列表。

2.1 使用 compile 加速

compile( rule [,flag] )

將正則規則編譯成一個 Pattern 物件,以供接下來使用。

第一個引數是規則式,第二個引數是規則選項。

返回一個 Pattern 物件

直接使用 findall ( rule , target ) 的方式來匹配字串,一次兩次沒什麼,如果是多次使用的話,由於正則引擎每次都要把規則解釋一遍,而規則的解釋又是相當費時間的,所以這樣的效率就很低了。如果要多次使用同一規則來進行匹配的話,可以使用 re.compile 函式來將規則預編譯,使用編譯過返回的 Regular Expression Object 或叫做 Pattern 物件來進行查詢。

>>> s='111,222,aaa,bbb,ccc333,444ddd'

>>> rule=r’/b/d+/b’

>>> compiled_rule=re.compile(rule)

>>> compiled_rule.findall(s)

['111', '222']

可見使用 compile 過的規則使用和未編譯的使用很相似。 compile 函式還可以指定一些規則標誌,來指定一些特殊選項。多個選項之間用 ’| ’ (位或)連線起來。

I       IGNORECASE 忽略大小寫區別。

L   LOCAL   字符集本地化。這個功能是為了支援多語言版本的字符集使用環境的,比如在轉義符/w ,在英文環境下,它代表[a-zA-Z0-9] ,即所以英文字元和數字。如果在一個法語環境下使用,預設設定下,不能匹配 "é" 或 "ç" 。 加上這 L 選項和就可以匹配了。不過這個對於中文環境似乎沒有什麼用,它仍然不能匹配中文字元。

M     MULTILINE  多行匹配。在這個模式下 ’^’( 代表字串開頭 ) 和 ’$’( 代表字串結尾 ) 將能夠匹配多行的情況,成為行首和行尾標記。比如

>>> s=’123 456/n789 012/n345 678’

>>> rc=re.compile(r’^/d+’)    # 匹配一個位於開頭的數字,沒有使用 M 選項

>>> rc.findall(s)

['123']             # 結果只能找到位於第一個行首的 ’123’

>>> rcm=re.compile(r’^/d+’,re.M)       # 使用 M 選項

>>> rcm.findall(s)

['123', '789', '345']  # 找到了三個行首的數字

同樣,對於 ’$’ 來說,沒有使用 M 選項,它將匹配最後一個行尾的數字,即 ’678’ ,加上以後,就能匹配三個行尾的數字 456 012 和 678 了 .

>>> rc=re.compile(r’/d+$’)

>>> rcm=re.compile(r’/d+$’,re.M)

>>> rc.findall(s)

['678']

>>> rcm.findall(s)

['456', '012', '678']

S     DOTALL       ‘.’ 號將匹配所有的字元。預設情況下 ’.’ 匹配除換行符 ’/n’ 外的所有字元,使用這一選項以後, ’.’ 就能匹配包括 ’/n’ 的任何字元了。

U    UNICODE       /w /W /b /B /d /D /s 和 /S 都將使用Unicode 。

X     VERBOSE      這個選項忽略規則表示式中的空白,並允許使用 ’#’ 來引導一個註釋。這樣可以讓你把規則寫得更美觀些。比如你可以把規則

>>> rc
 = re.compile(
r
"
/d+|[a-zA-Z]+
")
       
#
匹配一個數字或者單詞

使用 X 選項寫成:

>>> rc
 = re.compile(r"""
  
# start a rule

/d+
      

    
         

# 
number

| 
[a-zA-Z]+
   

        

# 
word

""", re.VERBOSE)

在這個模式下,如果你想匹配一個空格,你必須用'/ '
的形式('/'
後面跟一個空格) 

2.2 match 與 search

match( rule , targetString [,flag] )

search( rule , targetString [,flag] )

(注: re 的 match 與 search 函式同 compile 過的 Pattern 物件的 match 與 search 函式的引數是不一樣的。 Pattern 物件的 match 與 search 函式更為強大,是真正最常用的函式)

按照規則在目標字串中進行匹配。

第一個引數是正則規則,第二個是目標字串,第三個是選項(同 compile 函式的選項)

返回:若成功返回一個 Match 物件,失敗無返回

findall 雖然很直觀,但是在進行更復雜的操作時,就有些力不從心了。此時更多的使用的是 match 和 search 函式。他們的引數和 findall 是一樣的,都是:

match( rule , targetString [,flag] )

search( rule , targetString [,flag] )

不過它們的返回不是一個簡單的字串列表,而是一個 MatchObject (如果匹配成功的話) . 。通過操作這個 matchObject ,我們可以得到更多的資訊。

需要注意的是,如果匹配不成功,它們則返回一個 NoneType 。所以在對匹配完的結果進行操作之前,你必需先判斷一下是否匹配成功了,比如:

>>> m=re.match( rule , target )

>>> if m:                       # 必需先判斷是否成功

        doSomethin

這兩個函式唯一的區別是: match 從字串的開頭開始匹配,如果開頭位置沒有匹配成功,就算失敗了;而 search 會跳過開頭,繼續向後尋找是否有匹配的字串。針對不同的需要,可以靈活使用這兩個函式。

關於 match 返回的 MatchObject 如果使用的問題,是 Python 正則式的精髓所在,它與組的使用密切相關。我將在下一部分詳細講解,這裡只舉個最簡單的例子:

例:

>>> s= 'Tom:9527 , Sharry:0003'

>>> m=re.match( r'(?P<name>/w+):(?P<num>/d+)' , s )

>>> m.group()

'Tom:9527'

>>> m.groups()

('Tom', '9527')

>>> m.group(‘name’)

'Tom'

>>> m.group(‘num’)

'9527'

2.3 finditer

finditer( rule , target [,flag] )

引數同 findall

返回一個迭代器

finditer 函式和 findall 函式的區別是, findall 返回所有匹配的字串,並存為一個列表,而 finditer 則並不直接返回這些字串,而是返回一個迭代器。關於迭代器,解釋起來有點複雜,還是看看例子把:

>>> s=’111 222 333 444’

>>> for i in re.finditer(r’/d+’ , s ):

        print i.group(),i.span()          # 列印每次得到的字串和起始結束位置

結果是

111 (0, 3)

222 (4, 7)

333 (8, 11)

444 (12, 15)

簡單的說吧,就是 finditer 返回了一個可呼叫的物件,使用 for i in finditer() 的形式,可以一個一個的得到匹配返回的 Match 物件。這在對每次返回的物件進行比較複雜的操作時比較有用。

2.4 字串的替換和修改

re 模組還提供了對字串的替換和修改函式,他們比字串物件提供的函式功能要強大一些。這幾個函式是

sub ( rule , replace , target [,count] )

subn(rule , replace , target [,count] )

在目標字串中規格規則查詢匹配的字串,再把它們替換成指定的字串。你可以指定一個最多替換次數,否則將替換所有的匹配到的字串。

第一個引數是正則規則,第二個引數是指定的用來替換的字串,第三個引數是目標字串,第四個引數是最多替換次數。

這兩個函式的唯一區別是返回值。

sub 返回一個被替換的字串

sub 返回一個元組,第一個元素是被替換的字串,第二個元素是一個數字,表明產生了多少次替換。

例,將下面字串中的 ’dog’ 全部替換成 ’cat’

>>> s=’ I have a dog , you have a dog , he have a dog ‘

>>> re.sub( r’dog’ , ‘cat’ , s )

' I have a cat , you have a cat , he have a cat '

如果我們只想替換前面兩個,則

>>> re.sub( r’dog’ , ‘cat’ , s , 2 )

' I have a cat , you have a cat , he have a dog '

或者我們想知道發生了多少次替換,則可以使用 subn

>>> re.subn( r’dog’ , ‘cat’ , s )

(' I have a cat , you have a cat , he have a cat ', 3)

split( rule , target [,maxsplit] )

切片函式。使用指定的正則規則在目標字串中查詢匹配的字串,用它們作為分界,把字串切片。

第一個引數是正則規則,第二個引數是目標字串,第三個引數是最多切片次數

返回一個被切完的子字串的列表

這個函式和 str 物件提供的 split 函式很相似。舉個例子,我們想把上例中的字串被 ’,’ 分割開,同時要去掉逗號前後的空格

>>> s=’ I have a dog   ,   you have a dog  ,  he have a dog ‘

>>> re.split( ‘/s*,/s*’ , s )

[' I have a dog', 'you have a dog', 'he have a dog ']

結果很好。如果使用 str 物件的 split 函式,則由於我們不知道 ’,’ 兩邊會有多少個空格,而不得不對結果再進行一次處理。

escape( string )

這是個功能比較古怪的函式,它的作用是將字串中的 non-alphanumerics 字元(我已不知道該怎麼翻譯比較好了)用反義字元的形式顯示出來。有時候你可能希望在正則式中匹配一個字串,不過裡面含有很多 re 使用的符號,你要一個一個的修改寫法實在有點麻煩,你可以使用這個函式 ,

例 在目標字串 s 中匹配 ’(*+?)’ 這個子字串

>>> s= ‘111 222 (*+?) 333’

>>> rule= re.escape( r’(*+?)’ )

>>> print rule

/(/*/+/?/)

>>> re.findall( rule , s )

['(*+?)']

3     更深入的瞭解 re 的組與物件

前面對 Python 正則式的組進行了一些簡單的介紹,由於還沒有介紹到 match 物件,而組又是和 match 物件密切相關的,所以必須將它們結合起來介紹才能充分地說明它們的用途。

不過再詳細介紹它們之前,我覺得有必要先介紹一下將規則編譯後的生成的 patter 物件

3.1 編譯後的 Pattern 物件

將一個正則式,使用 compile 函式編譯,不僅是為了提高匹配的速度,同時還能使用一些附加的功能。編譯後的結果生成一個 Pattern 物件,這個物件裡面有很多函式,他們看起來和 re 模組的函式非常象,它同樣有 findall , match , search ,finditer , sub , subn , split 這些函式,只不過它們的引數有些小小的不同。一般說來, re 模組函式的第一個引數,即正則規則不再需要了,應為規則就包含在 Pattern 物件中了,編譯選項也不再需要了,因為已經被編譯過了。因此 re 模組中函式的這兩個引數的位置,就被後面的引數取代了。

findall , match , search 和 finditer 這幾個函式的引數是一樣的,除了少了規則和選項兩個引數外,它們又加入了另外兩個引數,它們是:查詢開始位置和查詢結束位置,也就是說,現在你可以指定查詢的區間,除去你不感興趣的區間。它們現在的引數形式是:

findall ( targetString [, startPos [,endPos] ] )

finditer ( targetString [, startPos [,endPos] ] )

match ( targetString [, startPos [,endPos] ] )

search ( targetString [, startPos [,endPos] ] )

這些函式的使用和 re 模組的同名函式使用完全一樣。所以就不多介紹了。

除了和 re 模組的函式同樣的函式外, Pattern 物件還多了些東西,它們是:

flags       查詢編譯時的選項

pattern 查詢編譯時的規則

groupindex 規則裡的組

這幾個不是函式,而是一個值。它們提供你一些規則的資訊。比如下面這個例子

>>> p=re.compile( r'(?P<word>/b[a-z]+/b)|(?P<num>/b/d+/b)|(?P<id>/b[a-z_]+/w*/b)' , re.I )

>>> p.flags

2

>>> p.pattern

'(?P<word>//b[a-z]+//b)|(?P<num>//b//d+//b)|(?P<id>//b[a-z_]+//w*//b)'

>>> p.groupindex

{'num': 2, 'word': 1, 'id': 3}

我們來分析一下這個例子:這個正則式是匹配單詞、或數字、或一個由字母或 ’_’ 開頭,後面接字母或數字的一個 ID 。我們給這三種情況的規則都包入了一個命名組,分別命名為 ’word’ , ‘num’ 和 ‘id’ 。我們規定大小寫不敏感,所以使用了編譯選項 ‘I’ 。

編譯以後返回的物件為 p ,通過 p.flag 我們可以檢視編譯時的選項,不過它顯示的不是 ’I’ ,而是一個數值 2 。其實 re.I 是一個整數, 2 就是它的值。我們可以檢視一下:

>>> re.I

2

>>> re.L

4

>>> re.M

8

每個選項都是一個數值。

通過 p.pattern 可以檢視被編譯的規則是什麼。使用 print 的話會更好看一些

>>> print p.pattern

(?P<word>/b[a-z]+/b)|(?P<num>/b/d+/b)|(?P<id>/b[a-z_]+/w*/b)

看,和我們輸入的一樣。

接下來的 p.groupindex 則是一個字典,它包含了規則中的所有命名組。字典的 key 是名字, values 是組的序號。由於字典是以名字作為 key ,所以一個無命名的組不會出現在這裡。

3.2 組與 Match 物件

組與 Match 物件是 Python 正則式的重點。只有掌握了組和 Match 物件的使用,才算是真正學會了 Python 正則式。

3.2.1 組的名字與序號

正則式中的每個組都有一個序號,它是按定義時從左到右的順序從 1 開始編號的。其實, re 的正則式還有一個 0 號組,它就是整個正則式本身。

我們來看個例子

>>> p=re.compile( r’(?P<name>[a-z]+)/s+(?P<age>/d+)/s+(?P<tel>/d+).*’ , re.I )

>>> p.groupindex

{'age': 2, 'tel': 3, 'name': 1}

>>> s=’Tom 24 88888888  <=’

>>> m=p.search(s)

>>> m.groups()                           # 看看匹配的各組的情況

('Tom', '24', '8888888')

>>> m.group(‘name’)                   # 使用組名獲取匹配的字串

‘Tom’

>>> m.group( 1 )                         # 使用組序號獲取匹配的字串,同使用組名的效果一樣

>>> m.group(0)                           # 0 組裡面是什麼呢?

'Tom 24 88888888  <='

原來 0 組就是整個正則式 , 包括沒有被包圍到組裡面的內容。當獲取 0 組的時候,你可以不寫這個引數。 m.group(0) 和 m.group() 的效果是一樣的:

>>> m.group()

'Tom 24 88888888  <='

接下來看看更多的 Match 物件的方法,看看我們能做些什麼。

3.2.2 Match 物件的方法

group([index|id]) 獲取匹配的組,預設返回組 0, 也就是全部值

groups()                返回全部的組

groupdict()            返回以組名為 key ,匹配的內容為 values 的字典

接上例:

>>> m.groupindex()

{'age': '24', 'tel': '88888888', 'name': 'Tom'}

start( [group] )     獲取匹配的組的開始位置

end( [group] )              獲取匹配的組的結束位置

span( [group] )     獲取匹配的組的(開始,結束)位置

expand( template ) 根據一個模版用找到的內容替換模版裡的相應位置

這個功能比較有趣,它根據一個模版來用匹配到的內容替換模版中的相應位置,組成一個新的字串返回。它使用 /g<index|name> 或 /index 來指示一個組。

接上例

>>> m.expand(r'name is /g<1> , age is /g<age> , tel is /3')

'name is Tom , age is 24 , tel is 88888888'

除了以上這些函式外, Match 物件還有些屬性

pos          搜尋開始的位置引數

endpos   搜尋結束的位置引數

這兩個是使用 findall 或 match 等函式時,傳入的引數。在上面這個例子裡,我們沒有指定開始和結束位置,那麼預設的開始位置就是 0, 結束位置就是最後。

>>> m.pos

0

>>> m.endpos

19

lastindex 最後匹配的組的序號

>>> m.lastindex

3

lastgroup       最後匹配的組名

>>> m.lastgroup

'tel'

re     產生這個匹配的 Pattern 物件,可以認為是個逆引用

>>> m.re.pattern

'(?P<name>[a-z]+)//s+(?P<age>//d+)//s+(?P<tel>//d+).*'

得到了產生這個匹配的規則

string 匹配的目標字串

>>> m.string

'Tom 24 88888888  <='