1. 程式人生 > >Python——正則表示式(2)

Python——正則表示式(2)

======================================================================================

3.使用正則表示式

現在,我們已經學習了一些簡單的正則表示式,但我們應該怎麼在Python中使用它們呢?re模組提供了一個連線正則表示式引擎的介面,允許你將RE編譯成物件並利用它們進行匹配。

-----------------------------------------------------------------------------------------------------------------------------

-------------------------

3.1.編譯正則表示式

正則表示式被編譯成模式物件,該物件擁有很多方法來進行各種各樣的操作,比如按照模式匹配查詢或者執行字串的替換。

>>> import re
>>> p = re.compile('ab*')
>>> p
re.compile('ab*')
re.compile()方法也可以接受一個可選flags引數,用於指定各種特殊功能和語法變更,我們將會在之後一一學習。現在我們看一個簡單的例子:
>>> p = re.compile('ab*',re.IGNORECASE)
正則表示式作為一個字串引數傳遞給re.compile()方法。由於正則表示式並不是Python的核心部分,也沒有特殊的語法去表示它們,所以只能被作為字串處理(有許多應用並不需要RE,所以沒有必要把RE納入Python的核心部分),相反,正則表示式re模組僅作為C的擴充套件模組嵌入的Python中,就像socket或者zlib模組。

把正則表示式作為字串也讓Python更加簡潔,但是也有一個缺點,下邊我們就來談一談。
----------------------------------------------------------------------------------------------------

--------------------------------------------------

3.2.麻煩的反斜槓
就像上文描述的,正則表示式使用反斜槓 \ 來使一些字元擁有特殊的意義(比如\s)或者去掉特殊字元的特殊意義(比如\*就是表示星號而沒有特殊的意義),這會與Python字串中實現相同功能的字元發生衝突。

假如我們要寫一個RE來匹配LaTeX檔案中的一個字串“\section”,首先你要先在程式程式碼中寫出將要匹配的字串。接著,你需要在反斜槓以及其他元字元前面加上反斜槓以去掉它們的特殊含義,所以得到結果“\\section”,這個字串將傳遞給re.compile()函式。然而,要知道Python字串中的反斜槓也有特殊意義,所以要再次在兩個反斜槓前面加上反斜槓,得到字串“\\\\section”。

匹配字串
匹配步驟
\section 將要匹配的字串
\\section 正則表示式中用‘\\’表示‘\’
“\\\\section” Python字串中也用‘\\’表示‘\’
總之,要匹配一個反斜槓字元‘\’,你需要寫四個反斜槓‘\\\\’作為一個正則表示式字串,因為正則表示式必須為雙斜槓 \\ ,而每個反斜槓在Python字串中也要用雙斜槓‘\\’表示。這就造成了我們需要重複很多次反斜槓,也讓最後的正則表示式字串難於理解。

解決方法是使用Python中的原始字串。所謂原始字串,即在字串最前面加上字母r,這樣字串中的反斜槓都會被去掉特殊語義,看做普通字元。比如,字串 r”\n” 是包含‘\’和‘n’兩個字元的字串,而字串 “\n” 是隻有一個換行符的字串。正則表示式通常使用Python中的原始字串來表示。

正則表示式字串
原始字串
“ab*” r”ab*”
“\\\\section” r”\\section”
“\\w+\\s+\\1” r”\w+\s+\1”
------------------------------------------------------------------------------------------------------------------------------------------------------

3.3.執行匹配
將正則表示式編譯之後會得到一個模式物件,那麼你會用它做什麼呢?模式物件包含許多方法和屬性,我們在這裡只介紹最常用的幾個,你可以通過檢視re模組的文件來檢視完整的列表。

方法/屬性
功能
match() 判斷一個正則表示式是否從開始處匹配一個字串
search() 掃描一個字串,找到正則表示式匹配的第一個位置
findall() 掃描一個字串,找到匹配正則表示式的所有子字串,並將它們以列表的形式返回
finditer() 掃描一個字串,找到匹配正則表示式的所有子字串,並將它們以迭代器的形式返回
如果沒有找到匹配的字串,match()和search()方法會返回None。如果匹配成功,則會返回一個匹配物件,包含匹配的資訊:起始位置和匹配的子字串等等。

你可以在互動模式下使用re模組來學習這些內容。如果你能夠使用tkinter,你可以看一下Tools/demo/redemo.py這個程式,這是隨著Python釋出的一個示例程式。它可以讓你輸入正則表示式和字串,並輸出兩者是否匹配。當你測試一個複雜的正則表示式的時候,redemo.py是非常有用的。Phil Schwartz’s Kodos也是一個開發和測試正則表示式的一個很有用的互動式工具。

我們使用標準Python直譯器來解釋這些例子。首先,開啟Python直譯器,匯入re模組,然後編譯一個RE:

>>> import re
>>> p = re.compile('[a-z]+')
>>> p
re.compile('[a-z]+')
現在你可以利用正則表示式 [a-z]+ 來匹配各種字串。但是一個空的字串並不能被匹配,因為加號 + 表示重複1次以上,在這種情況下,match()方法將會返回None。另外,這個結果在直譯器中不會輸出,不過你可以明確地呼叫print()方法來輸出這個結果。
>>> p.match('')
>>> print(p.match(''))
None
接著,讓我們嘗試一個它可以匹配的字串,比如字串“tempo”。這種情況下,match()方法將會返回一個匹配物件(match  object),為了之後使用這個物件,你應該把這個結果儲存在一個變數中。
>>> m = p.match('tempo')
>>> m
<_sre.SRE_Match object; span=(0, 5), match='tempo'>

現在你可以利用匹配物件查詢匹配字串的資訊。匹配物件例項也有一些方法和屬性,這裡列出最重要的幾個:

方法/屬性 功能
group() 返回匹配的字串
start() 返回字串匹配的開始位置
end() 返回字串匹配的結束位置
span() 返回一個元祖表示匹配位置,(開始,結束)

嘗試以下這些例子,你可以很快理解這些方法:
>>> m.group()
'tempo'
>>> m.start(),m.end()
(0, 5)
>>> m.span()
(0, 5)
group()方法返回由RE.start()和RE.end()位置確定的子字串。span()方法用一個元祖返回子字串的開始位置和結束位置。但要注意的是,match()方法是判斷正則表示式是否從開始處匹配字串,所以start()方法總是返回0。然而,search()方法就不一樣了,它掃描整個字串,匹配的子字串的開始位置不一定是0。
>>> print(p.match(':::message'))
None
>>> m = p.search(':::message')
>>> print(m)
<_sre.SRE_Match object; span=(3, 10), match='message'>
>>> m.group()
'message'
>>> m.span()
(3, 10)
在實際的程式中,最常用的寫法是將匹配物件儲存在一個變數中,然後檢查它是否為None。就像下邊這樣:
p = re.compile(…)
m = p.match(‘string  goes  here’)
if m:
print(‘Match  found: ’ , m.group())
else:
    print(‘No  match’)
有兩個方法可以返回所有匹配的子字串。findall()方法返回所有匹配字串的列表:
>>> p = re.compile('\d+')
>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']
findall()方法需要在它返回結果之前創建出整個列表。但是finditer()方法將匹配物件作為迭代器返回(譯者注:迭代器的方式更節省記憶體,效率更高)。
>>> iterator = p.finditer('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
>>> iterator
<callable_iterator object at 0x036A2110>
>>> for match in iterator:
	print(match.span())

	
(0, 2)
(22, 24)
(40, 42)
------------------------------------------------------------------------------------------------------------------------------------------------------
3.4.模組級別的函式
你並不需要去建立一個匹配物件來呼叫它的方法,re模組中也提供一些全域性函式比如match()、search()、findadd()、sub()等等。這些函式的第一個引數是正則表示式字串,其他引數跟模式物件同名的方法採用一樣的引數,返回值也一樣,都是返回None或者匹配物件。

>>> print(re.match(r'From\s+','Fromage amk'))
None
>>> print(re.match(r'From\S+','Fromage amk'))
<_sre.SRE_Match object; span=(0, 7), match='Fromage'>
>>> print(re.match(r'From\S+','Fromage amk').group())
Fromage
>>> re.match(r'From\s+','From amk Thu May 14 19:12:10 1998')
<_sre.SRE_Match object; span=(0, 5), match='From '>
其實,這些函式只是簡單地為你建立了一個模式物件,並且可以呼叫其相關函式。另外,它將編譯好的模式物件存放在快取中,所以如果之後你使用了相同的正則表示式,就不用了再次建立模式了,可以實現快速呼叫。

那麼你應該使用這些模組級別的函式呢?還是應該先編譯得到自己模式物件再呼叫它的方法呢?如果你要在一個迴圈中使用正則表示式,提前編譯它可以節省函式的呼叫。但在迴圈外部,由於內部緩衝機制,兩者的效率不差上下。

------------------------------------------------------------------------------------------------------------------------------------------------------
3.5.編譯標誌
編譯標誌可以讓你在一些方面改變正則表示式的工作方法。編譯標誌在re模組中有兩個可用名稱:全稱和簡寫,比如IGNORECASE的簡寫是字母I(如果熟悉Perl語言的模式編寫,你會知道Perl語言的簡寫和這個一樣,比如re.VERBOSE和簡寫是re.X)。多個編譯標誌可以通過邏輯或連線起來,比如re.I | re.M 設定了I和M兩個標誌。

下表列出了一些可用的編譯標誌:
編譯標誌
含義
ASCII,A 使得轉義符號如\w,\b,\s和\d只匹配ASCII字元
DOTALL,S 使得點號 . 可以匹配任何符號,包括換行符
IGNORECASE,I 匹配不區分大小寫
LOCALE,L 支援當前的語言(區域)設定
MULTILINE,M 多行匹配,會影響^和$
VERBOSE,X(for ‘extended’) 啟用詳細的正則表示式

I
IGNORECASE

匹配不區分大小寫;使得字元類和文字字串在匹配字元的時候不區分大小寫。例如,[A-Z]也會匹配小寫字母,Spam會匹配Spam、spam和spAM。如果你不設定LOCALE標誌,則不會考慮語言(區域)設定方法的問題。

L
LOCALE

使得\w、\W、\b和\B取決於當前的語言環境,而不是Unicode資料庫。

區域設定是C語言庫的一個功能,主要為了在編寫程式的時候考慮語言的差異性。比如,如果你正在處理一個法文文字,你想要寫 \w+ 來匹配單詞,但是 \w 只匹配出現在字元類[a-zA-Z]中的單詞,它不會匹配 'é' 或者 'ç’ ,如果你的系統被設定為法語語言環境,那麼C語言函式將會認為 'é' 也是一個字母。當編譯正則表示式的時候設定了LOCALE標誌,\w 就可以識別法文了,但是它的速度相對要慢一點。

M
MULTILINE

(^和$我們還沒有提到,它們將在後面講解)

通常的,^只匹配字串的開頭,而$只匹配字串的結尾,當這個標誌設定的時候,元字元^將會匹配字串中每一行的的行首,而類似的,元字元$將會匹配每一行的行尾。

S
DOTALL

使得點號‘.’匹配所有的字元,包括換行符。如果不設定這個標誌,點號‘.’將匹配除了換行符之外的所有字元。

A
ASCII

使得\w、\w、\b、\B和\S值匹配ASCII字元,而不是Unicode字元。這個標誌僅對Unicode模式有意義,並忽略位元組模式。

X
VERBOSE

這個標誌可以讓你組織正則表示式更具靈活性,從而寫的正則表示式更具有可讀性。如果設定了這個標誌,正則表示式中的空格將會被忽略,當然這裡不包括字元類中的空格,也不包括被反斜槓轉義的空格,這會讓你更清晰地去組織正則表示式。另外,這個標誌也允許在正則表示式式中使用註釋,井號 # 以及之後的字元將會被正則表示式忽略,除非井號 # 在字元類中或者經過了反斜槓轉義。

下面看一個使用re.VERBOSE的例子:
>>> charref = re.compile(r'''
&[#]                   #開始數字引用
(
	0[0-7]+         #八進位制格式
       |[0-9]+          #十進位制格式
       |x[0-9a-fA-F]+     #十六進位制格式
)
;                      #結尾分號
''',re.VERBOSE)
如果不用VERBOSE設定,這個正則表示式將會是下面這個格式:
>>> charref = re.compile('&[#](0[0-7]+'
		         '|[0-9]+'
		         '|x[0-9a-fA-F]+);')
在上述的例子中,我們使用了Python自動串聯字串的功能,從而將正則表示式分成了幾個更小的部分,但是它依舊沒有使用re.VERBOSE版本的RE好理解。