文章目錄
寫這個系列是為了整理在Python中學習Regex的知識。其實在Linux Shell、PHP以及Qt的學習中都接觸過Regex,但是由於筆記做得不好,所以經常到了使用的時候翻來翻去,很是麻煩,所以把知識梳理一下。本文主要翻譯自Python標準庫手冊中的指南,但是沒有逐字句地翻譯,它比標準庫中的re模組相應介紹要簡單一些,也更容易看懂。
----------------------------------
Regex入門
本文檔介紹使用Python中的re模組實現Regex,它比Python庫手冊中的相應章節相對容易一些。
引言
Re模組在Python1.5中加入,它提供了一個Perl風格的Regex功能。更早版本的Python使用的是regex模組,它提供的是Emacs風格的匹配。在Python2.5中,regex模組被移除。
Regex(Regular expression,又稱為RE、regexe或regex pattern)是一個小巧、高度特化的編程方法,它內嵌在Python中,通過re模組實現。使用這個方法可以匹配特定格式的字串,包括英語句子、電子郵件地址、TeX命令等等,可以檢查字串是否符合該格式,或在字串中尋找符合該格式的子串,也可以按照格式修改或分割字串。
Regex的“格式”(pattern)在運行時被由C語言寫成的匹配引擎轉換成位元組碼。對於進階應用程式,應當注意該引擎如何產生給定的Regex,並用特定方式書寫該運算式以產生更高效的位元組碼。不過,本文檔不涉及最佳化的問題,因為這需要對匹配引擎的內部工作原理有較好的理解。
Regex小巧且嚴格,所以不是所有的字串處理都要使用它;有些問題用Regex可以解決,但會讓問題變得複雜。在這些情況下,就不要使用Regex處理。儘管使用Python編寫的代碼比精巧設計的Regex慢,但它更易於理解。
簡單的格式
我們從最簡單的Regex開始學習。由於它主要用來處理字串,我們將首先學習最普遍的情況:匹配字元。電腦科學以Regex為基礎,關於它的詳細解釋可以參考關於書寫編譯器的幾乎任何課本。
匹配字元
大多數字母和符號可以匹配自身,可以將匹配模式選擇為大小寫敏感或不敏感,詳細內容見後。
這條規範有一個例外:有一些字元是特殊的“元字元”,不能匹配自身,它們表示非常規的意思,或者能夠通過重複或者改變其意思影響Regex的其他成分。本文檔專註於討論不同的元字元及其含義。
下面是這些元字元,它們的意思該在本文以後的部分加以介紹。
. ^ $ * + ? { } [ ] \ | ( )
首先來看[和]。它們用來表示一個字元的集合,集合內的任意一個字元都可以參與匹配,其中字元可以單獨列出來,或者用-來表示範圍。例如,[abc]將匹配其中的任何一個字元a、b或c,它等同於用範圍表示的[a-c]。如果匹配任何小寫字母,可以寫成[a-z]。
在[]中的元字元不起作用。如[akm$]將匹配a、k、m和$:$雖然是一個元字元,但是在這個集合中失去了特殊意義。
可以通過取補的方式匹配不在集合中的字元,方法是在該集合內的最前面加^,如果加在整個集合的前面則會匹配^。例如,[^5]將匹配除了5的任何字元。
最常用的元字元是反斜線\。在Python中,字元前加\表示特殊含義;它也可以用於元字元前表示去除元字元的特殊含義。例如,如果試圖匹配[或\,可以分別通過\[和\\來實現。
一些特殊序列以\開始,表示一些常用的預定義字串,如下所示:
\d |
十進位數字,相當於[0-9] |
\D |
非十進位數字,相當於[^0-9] |
\s |
空白字元,相當於[\t\n\r\v] |
\S |
非空白字元,相當於[^\t\n\r\f\v] |
\w |
字母+數字,相當於[a-zA-Z0-9] |
\W |
非字母非數字,相當於[^a-zA-Z0-9] |
這些特殊序列可以在集合中使用。如[\s,.]匹配任何空白字元、,或者.。
本節中最後一個元字元是.,它匹配除新行外的任何字元(在可選用的模組(re.DOTALL)中也可以匹配新行)。當試圖匹配任何字元時使用.。
多次匹配
匹配不同的字元集合是字串中的函數無法完成而Regex可以完成的第一件事,但如果僅僅如此,Regex還顯得功能簡陋。它另外一個功能是可以匹配特定次數的字元。
多次匹配的第一個元字元是*,它不是去匹配字元本身,而是表示前一個字元可以匹配0次或多次。例如ca*t將匹配ct(0次)、cat(1次)、caaat(3次)或更多。由於C語言中整型長度的限制,Regex的匹配引擎內部規定不能超過20億次的匹配,當然你可能並沒有足夠的記憶體去儲存這麼長的字串,所以不用理會這個限定。
*的匹配是“貪婪”的:當匹配時,引擎會試圖匹配更多次的字元;如果在較多次數下的匹配不成功(格式中的下一部分不能匹配時),才會試圖匹配較少的重複次數。
逐步講解的例子會使這種設計看上去合理。考慮a[bcd]*b,它匹配以a開始,中間含有0次或多次的b、c或者d,並最終以b結束。在字串abcbd中的匹配過程如下:
步驟 |
已匹配 |
說明 |
1 |
a |
a存在于格式中。 |
2 |
abcbd |
引擎匹配了[bcd]*,試圖匹配盡量多次數,從而達到了字串尾。 |
3 |
匹配失敗 |
引擎試圖匹配b,但當前位置為字串的串尾,所以匹配失敗。 |
4 |
abcb |
回頭重新匹配,使[bcd]*匹配次數在上次匹配的基礎上減1。 |
5 |
匹配失敗 |
再次試圖匹配b,但是當前位置為最後一個字元d,匹配失敗。 |
6 |
abc |
回頭再次重新匹配,這次[bcd]*只匹配到bc。 |
7 |
abcb |
嘗試匹配b,這次當前位置上為b,匹配成功。 |
到達Regex的結尾時,匹配abcb成功。以上過程表明“貪婪匹配”如果失敗,則會一次重新匹配儘可能多次的格式,直到嘗試匹配0次[bcd]*,如果在0次匹配的情況下後續字元依然無法匹配,則字元匹配失敗。
另外一個重複匹配的元字元是+,它匹配一次或者多次。注意*和+的區別。
另外,?表示匹配0次或1次,可以把它想像成某種可選擇性的場合,如home-?brew可以匹配homebrew或home-brew。
最常用的匹配次數運算式為{m,n},其中m和n為十進位數字,表示最少重複m次最多重複n次。例如a/{1,3}b可以匹配a/b、a//b和a///b,而對a////b不能匹配。M和n中的一個可以省略掉:省略掉m表示最少匹配0次,省略掉n表示最多匹配無窮次(實際上是20億次)。
很容易看出{0,}與*意義相同,類似地,{1,}與+、{0,1}與?意義都相同。這種情況下用單個字元更容易書寫,可讀性也更好。