Python自帶了正則表達式引擎(內置的re模塊),但是不支持一些高級特性,比如下面這幾個:
- 固化分組 Atomic grouping
- 占有優先量詞 Possessive quantifiers
- 可變長度的逆序環視 Variable-length lookbehind
- 遞歸匹配 Recursive patterns
- (起始/繼續)位置錨\G Search anchor
幸好,在2009年,Matthew Barnett寫了一個更強大正則表達式引擎——regex模塊,這是一個Python的第三方模塊。
除了上面這幾個高級特性,還有很多有趣、有用的東西,本文大致介紹一下,很多內容取自regex的文檔。
無論是編程還是文本處理,regex模塊都是一件利器。
用一個指標可以大致了解它的復雜性,re模塊有4270行C語言代碼,而regex模塊有24513行C語言代碼。
這個模塊以后可能被收進Python標准庫。目前(2015年)它還在不斷發展,經常發布bug修正版,不過感覺用在一般生產環境應該沒什么問題。
目錄
一、安裝regex
二、一些有趣的特性
三、模糊匹配
四、兩種工作模式
五、Version 0模式和re模塊不兼容之處
一、安裝regex
regex支持Python 2.5+和Python 3.1+,可以用pip命令安裝:
pip install regex
PyPy 2.6+也可以使用這個模塊。
regex基本兼容re模塊,現有的程序可以很容易切換到regex模塊:
import regex as re
二、一些有趣的特性
完整的Unicode支持
1,支持最新的Unicode標准,這一點經常比Python本身還及時。
2,支持Unicode代碼屬性,包括scripts和blocks。
如:\p{Cyrillic}表示西里爾字符(scripts),\p{InCyrillic}表示西里爾區塊(blocks)。
3,支持完整的Unicode字符大小寫匹配,詳見此文。
如:ss可匹配ß;cliff(這里的ff是一個字符)可匹配CLIFF(FF是兩個字符)。
不需要的話可以關閉此特性。不支持Unicode組合字符與單一字符的大小寫匹配,所以感覺這個特性不太實用。
4,regex.WORD標志開啟后:
作用1:\b、\B采用Unicode的分界規則,詳見此文。
如:開啟后\b.+?\b可搜索到3.4;關閉后小數點.成為分界符,於是只能搜到['3', '.', '4']。
作用2:采用Unicode的換行符。除了傳統的\r、\n,Unicode還有一些換行符,開啟后作用於.MULTILINE和.DOTALL模式。
5,\X匹配Unicode的單個字形(grapheme)。
Unicode有時用多個字符組合在一起表示一個字形,詳見此文。
\X匹配一個字形,如:^\X$可以匹配'\u0041\u0308'。
單詞起始位置、單詞結束位置
\b是單詞分界位置,但不能區分是起始還是結束位置。
regex用\m表示單詞起始位置,用\M表示單詞結束位置。
(?|...|...)
重置分支匹配中的捕獲組編號。
>>> regex.match(r"(?|(first)|(second))", "first").groups() ('first',) >>> regex.match(r"(?|(first)|(second))", "second").groups() ('second',)
兩次匹配都是把捕獲到的內容放到編號為1捕獲組中,在某些情況很方便。
(?flags-flags:...)
局部范圍的flag控制。在re模塊,flag只能作用於整個表達式,現在可以作用於局部范圍了:
>>> regex.search(r"<B>(?i:good)</B>", "<B>GOOD</B>") <regex.Match object; span=(0, 11), match='<B>GOOD</B>'>
在這個例子里,忽略大小寫模式只作用於標簽之間的單詞。
(?i:)是打開忽略大小寫,(?-i:)則是關閉忽略大小寫。
如果有多個flag挨着寫既可,如(?is-f:),減號左邊的是打開,減號右邊的是關閉。
除了局部范圍的flag,還有全局范圍的flag控制,如 (?si-f)<B>good</B>
re模塊也支持這個,可以參見Python文檔。
把flags寫進表達式、而不是以函數參數的方式聲明,方便直觀且不易出錯。
(?(DEFINE)...)
定義可重復使用的子句
>>> regex.search(r'(?(DEFINE)(?P<quant>\d+)(?P<item>\w+))(?&quant) (?&item)', '5 elephants') <regex.Match object; span=(0, 11), match='5 elephants'>
此例中,定義之后,(?&quant)表示\d+,(?&item)表示\w+。如果子句很復雜,能省不少事。
partial matches
部分匹配。可用於驗證用戶輸入,當輸入不合法字符時,立刻給出提示。
可以pickle編譯后的正則表達式對象
如果正則表達式很復雜或數量很多,編譯需要較長時間。
這時可以把編譯好的正則式用pickle存儲到文件里,下次使用直接pickle.load()就不必再次編譯了。
除了以上這些,還有很多新特性(匹配控制、便利方法等等),這里就不介紹了,請自行查閱文檔。
三、模糊匹配
regex有模糊匹配(fuzzy matching)功能,能針對字符進行模糊匹配,提供了3種模糊匹配:
- i,模糊插入
- d,模糊刪除
- s,模糊替換
以及e,包括以上三種模糊
舉個例子:
>>> regex.findall('(?:hello){s<=2}', 'hallo') ['hallo']
(?:hello){s<=2}的意思是:匹配hello,其中最多容許有兩個字符的錯誤。
於是可以成功匹配hallo。
這里只簡單介紹一下模糊匹配,詳情還是參見文檔吧。
四、兩種工作模式
regex有Version 0和Version 1兩個工作模式,其中的Version 0基本兼容現有的re模塊,以下是區別:
Version 0 (基本兼容re模塊) | Version 1 | |
啟用方法 | 設置.VERSION0或.V0標志,或者在表達式里寫上(?V0)。 |
設置.VERSION1或.V1標志,或者在表達式里寫上(?V1)。 |
零寬匹配 | 像re模塊那樣處理: .split 不能在零寬匹配處切割字符串。 .sub 在匹配零寬之后向前傳動一個位置。 |
.split 可以在零寬匹配處切割字符串。 .sub 采用正確的行為。 |
內聯flag | 內聯flag只能作用於整個表達式,不可關閉。 | 內聯flag可以作用於局部表達式,可以關閉。 |
字符組 | 只支持簡單的字符組。 | 字符組里可以有嵌套的集合,也可以做集合運算(並集、交集、差集、對稱差集)。 |
大小寫匹配 | 默認支持普通的Unicode字符大小寫,如Й可匹配й。 這與Python3里re模塊的默認行為相同。 |
默認支持完整的Unicode字符大小寫,如ss可匹配ß。 可惜不支持Unicode組合字符與單一字符的大小寫匹配,所以感覺這個特性不太實用。可以在表達式里寫上(?-f)關閉此特性。 |
如果什么設置都不做,會采用regex.DEFAULT_VERSION指定的模式。在目前,regex.DEFAULT_VERSION的默認值是regex.V0。
如果想默認使用V1模式,這樣就可以了:
import regex regex.DEFAULT_VERSION = regex.V1
V1模式默認開啟.FULLCASE(完整的忽略大小寫匹配)。通常用不上這個,所以在忽略大小寫匹配時用(?-f)關閉.FULLCASE即可,這樣速度更快一點,例如:(?i-f)tag
其中零寬匹配的替換操作差異比如明顯。絕大多數正則引擎采用的是Perl流派的作法,於是Version 1也朝着Perl的方向改過去了。
>>> # Version 0 behaviour (like re) >>> regex.sub('(?V0).*', 'x', 'test') 'x' >>> regex.sub('(?V0).*?', '|', 'test') '|t|e|s|t|' >>> # Version 1 behaviour (like Perl) >>> regex.sub('(?V1).*', 'x', 'test') 'xx' >>> regex.sub('(?V1).*?', '|', 'test') '|||||||||'
re模塊對零寬匹配的實現可能是有誤的(見issue1647489);
而V0零寬匹配的搜索和替換會出現不一致的行為(搜索采用V1的方式,替換采用re模塊的方式);
在Python 3.7+環境下,re和regex模塊的行為同時做了改變,據稱是采用了“正確”的方式處理零寬匹配:總是返回第一個匹配(字符串或零寬),但是如果是零寬並且之前匹配的還是零寬則忽略。這和3.6-的re模塊、regex的V0模式、V1模式都略有不同。
說着挺嚇人的,在實際使用中3.6- re、3.7+ re、V0、V1之間極少出現不兼容的現象。
五、Version 0模式和re模塊不兼容之處
上面說了“Version 0基本兼容re模塊”,說說不兼容的地方:
1、對零寬匹配的處理。
regex修復了re模塊的搜索bug(見issue1647489),但是也帶來了不兼容的問題。
在re中,用".*?"搜索"test"返回:['', '', '', '', ''],也就是:最前、字母之間的3個位置、最后,總共5個位置。
在regex中,則返回:['', 't', '', 'e', '', 's', '', 't', '']
在實際使用中,這個問題幾乎不會造成不兼容的情況,所以基本可以忽略此差異。
2、\s的范圍。
在re中,\s在這一帶的范圍是0x09 ~ 0x0D,0x1C ~ 0x1E。
在regex中,\s采用的是Unicode 6.3+標准的\p{Whitespace},在這一帶的范圍有所縮小,只有:0x09 ~ 0x0D。
十六進制 | 十進制 | 英文說明 | 中文說明 |
0x09 | 9 | HT (horizontal tab) | 水平制表符 |
0x0A | 10 | LF (NL line feed, new line) | 換行鍵 |
0x0B | 11 | VT (vertical tab) | 垂直制表符 |
0x0C | 12 | FF (NP form feed, new page) | 換頁鍵 |
0x0D | 13 | CR (carriage return) | 回車鍵 |
... | ... | ... | ... |
0x1C | 28 | FS (file separator) | 文件分割符 |
0x1D | 29 | GS (group separator) | 分組符 |
0x1E | 30 | RS (record separator) | 記錄分離符 |
除此之外,可能還有未知的不兼容之處。