Python的regex模块——更强大的正则表达式引擎
Python自帶了正則表達(dá)式引擎(內(nèi)置的re模塊),但是不支持一些高級特性,比如下面這幾個:
- 固化分組??? Atomic grouping
- 占有優(yōu)先量詞 ?? Possessive quantifiers
- 可變長度的逆序環(huán)視 ?? Variable-length lookbehind
- 遞歸匹配??? Recursive patterns
- (起始/繼續(xù))位置錨\G??? Search anchor
幸好,在2009年,Matthew Barnett寫了一個更強(qiáng)大正則表達(dá)式引擎——regex模塊,這是一個Python的第三方模塊。
除了上面這幾個高級特性,還有很多有趣、有用的東西,本文大致介紹一下,很多內(nèi)容取自regex的文檔。
?
無論是編程還是文本處理,regex模塊都是一件利器。
用一個指標(biāo)可以大致了解它的復(fù)雜性,re模塊有4270行C語言代碼,而regex模塊有24513行C語言代碼。
?
這個模塊以后可能被收進(jìn)Python標(biāo)準(zhǔn)庫。目前(2015年)它還在不斷發(fā)展,經(jīng)常發(fā)布bug修正版,不過感覺用在一般生產(chǎn)環(huán)境應(yīng)該沒什么問題。
?
目錄
一、安裝regex
二、一些有趣的特性
三、模糊匹配
四、兩種工作模式
五、Version 0模式和re模塊不兼容之處
?
一、安裝regex
regex支持Python 2.5+和Python 3.1+,可以用pip命令安裝:
pip install regexPyPy 2.6+也可以使用這個模塊。
?
regex基本兼容re模塊,現(xiàn)有的程序可以很容易切換到regex模塊:
import regex as re?
二、一些有趣的特性
完整的Unicode支持
1,支持最新的Unicode標(biāo)準(zhǔn),這一點(diǎn)經(jīng)常比Python本身還及時。
2,支持Unicode代碼屬性,包括scripts和blocks。
????? 如:\p{Cyrillic}表示西里爾字符(scripts),\p{InCyrillic}表示西里爾區(qū)塊(blocks)。
3,支持完整的Unicode字符大小寫匹配,詳見此文。
????? 如:ss可匹配?;cli?(這里的?是一個字符)可匹配CLIFF(FF是兩個字符)。
????? 不需要的話可以關(guān)閉此特性。不支持Unicode組合字符與單一字符的大小寫匹配,所以感覺這個特性不太實(shí)用。
4,regex.WORD標(biāo)志開啟后:
?? ?? 作用1:\b、\B采用Unicode的分界規(guī)則,詳見此文。
?????????????????? 如:開啟后\b.+?\b可搜索到3.4;關(guān)閉后小數(shù)點(diǎn).成為分界符,于是只能搜到['3', '.', '4']。
? ? ? 作用2:采用Unicode的換行符。除了傳統(tǒng)的\r、\n,Unicode還有一些換行符,開啟后作用于.MULTILINE和.DOTALL模式。
5,\X匹配Unicode的單個字形(grapheme)。
????? Unicode有時用多個字符組合在一起表示一個字形,詳見此文。
????? \X匹配一個字形,如:^\X$可以匹配'\u0041\u0308'。
?
單詞起始位置、單詞結(jié)束位置
\b是單詞分界位置,但不能區(qū)分是起始還是結(jié)束位置。
regex用\m表示單詞起始位置,用\M表示單詞結(jié)束位置。
?
(?|...|...)
重置分支匹配中的捕獲組編號。
兩次匹配都是把捕獲到的內(nèi)容放到編號為1捕獲組中,在某些情況很方便。
?
(?flags-flags:...)
局部范圍的flag控制。在re模塊,flag只能作用于整個表達(dá)式,現(xiàn)在可以作用于局部范圍了:
>>> regex.search(r"<B>(?i:good)</B>", "<B>GOOD</B>") <regex.Match object; span=(0, 11), match='<B>GOOD</B>'>在這個例子里,忽略大小寫模式只作用于標(biāo)簽之間的單詞。
(?i:)是打開忽略大小寫,(?-i:)則是關(guān)閉忽略大小寫。
如果有多個flag挨著寫既可,如(?is-f:),減號左邊的是打開,減號右邊的是關(guān)閉。
?
除了局部范圍的flag,還有全局范圍的flag控制,如?(?si-f)<B>good</B>
re模塊也支持這個,可以參見Python文檔。
把flags寫進(jìn)表達(dá)式、而不是以函數(shù)參數(shù)的方式聲明,方便直觀且不易出錯。
?
(?(DEFINE)...)
定義可重復(fù)使用的子句
>>> 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+。如果子句很復(fù)雜,能省不少事。
?
partial matches
部分匹配。可用于驗(yàn)證用戶輸入,當(dāng)輸入不合法字符時,立刻給出提示。
?
可以pickle編譯后的正則表達(dá)式對象
如果正則表達(dá)式很復(fù)雜或數(shù)量很多,編譯需要較長時間。
這時可以把編譯好的正則式用pickle存儲到文件里,下次使用直接pickle.load()就不必再次編譯了。
?
除了以上這些,還有很多新特性(匹配控制、便利方法等等),這里就不介紹了,請自行查閱文檔。
?
三、模糊匹配
regex有模糊匹配(fuzzy matching)功能,能針對字符進(jìn)行模糊匹配,提供了3種模糊匹配:
- i,模糊插入
- d,模糊刪除
- s,模糊替換
以及e,包括以上三種模糊
?
舉個例子:
>>> regex.findall('(?:hello){s<=2}', 'hallo') ['hallo'](?:hello){s<=2}的意思是:匹配hello,其中最多容許有兩個字符的錯誤。
于是可以成功匹配hallo。
?
這里只簡單介紹一下模糊匹配,詳情還是參見文檔吧。
?
四、兩種工作模式
regex有Version 0和Version 1兩個工作模式,其中的Version 0基本兼容現(xiàn)有的re模塊,以下是區(qū)別:
| ? | Version 0? (基本兼容re模塊) | Version 1 |
| 啟用方法 | 設(shè)置.VERSION0或.V0標(biāo)志,或者在表達(dá)式里寫上(?V0)。 | 設(shè)置.VERSION1或.V1標(biāo)志,或者在表達(dá)式里寫上(?V1)。 |
| 零寬匹配 | 像re模塊那樣處理: .split?不能在零寬匹配處切割字符串。 .sub 在匹配零寬之后向前傳動一個位置。 | 像Perl和PCRE那樣處理: .split?可以在零寬匹配處切割字符串。 .sub 采用正確的行為。 |
| 內(nèi)聯(lián)flag | 內(nèi)聯(lián)flag只能作用于整個表達(dá)式,不可關(guān)閉。 | 內(nèi)聯(lián)flag可以作用于局部表達(dá)式,可以關(guān)閉。 |
| 字符組 | 只支持簡單的字符組。 | 字符組里可以有嵌套的集合,也可以做集合運(yùn)算(并集、交集、差集、對稱差集)。 |
| 大小寫匹配 | 默認(rèn)支持普通的Unicode字符大小寫,如Й可匹配й。 這與Python3里re模塊的默認(rèn)行為相同。 | 默認(rèn)支持完整的Unicode字符大小寫,如ss可匹配?。 可惜不支持Unicode組合字符與單一字符的大小寫匹配,所以感覺這個特性不太實(shí)用。可以在表達(dá)式里寫上(?-f)關(guān)閉此特性。 |
如果什么設(shè)置都不做,會采用regex.DEFAULT_VERSION指定的模式。在目前,regex.DEFAULT_VERSION的默認(rèn)值是regex.V0。
如果想默認(rèn)使用V1模式,這樣就可以了:
import regex regex.DEFAULT_VERSION = regex.V1V1模式默認(rèn)開啟.FULLCASE(完整的忽略大小寫匹配)。通常用不上這個,所以在忽略大小寫匹配時用(?-f)關(guān)閉.FULLCASE即可,這樣速度更快一點(diǎn),例如:(?i-f)tag
?
其中零寬匹配的替換操作差異比如明顯。絕大多數(shù)正則引擎采用的是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模塊對零寬匹配的實(shí)現(xiàn)可能是有誤的(見issue1647489);
而V0零寬匹配的搜索和替換會出現(xiàn)不一致的行為(搜索采用V1的方式,替換采用re模塊的方式);
在Python 3.7+環(huán)境下,re和regex模塊的行為同時做了改變,據(jù)稱是采用了“正確”的方式處理零寬匹配:總是返回第一個匹配(字符串或零寬),但是如果是零寬并且之前匹配的還是零寬則忽略。這和3.6-的re模塊、regex的V0模式、V1模式都略有不同。
?
說著挺嚇人的,在實(shí)際使用中3.6- re、3.7+ re、V0、V1之間極少出現(xiàn)不兼容的現(xiàn)象。
?
五、Version 0模式和re模塊不兼容之處
上面說了“Version 0基本兼容re模塊”,說說不兼容的地方:
?
1、對零寬匹配的處理。
regex修復(fù)了re模塊的搜索bug(見issue1647489),但是也帶來了不兼容的問題。
?
在re中,用".*?"搜索"test"返回:['', '', '', '', ''],也就是:最前、字母之間的3個位置、最后,總共5個位置。
在regex中,則返回:['', 't', '', 'e', '', 's', '', 't', '']
在實(shí)際使用中,這個問題幾乎不會造成不兼容的情況,所以基本可以忽略此差異。
?
2、\s的范圍。
在re中,\s在這一帶的范圍是0x09 ~ 0x0D,0x1C ~ 0x1E。
在regex中,\s采用的是Unicode 6.3+標(biāo)準(zhǔn)的\p{Whitespace},在這一帶的范圍有所縮小,只有:0x09 ~ 0x0D。
| 十六進(jìn)制 | 十進(jìn)制 | 英文說明 | 中文說明 |
| 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) | 記錄分離符 |
?
除此之外,可能還有未知的不兼容之處。
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的Python的regex模块——更强大的正则表达式引擎的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 美团搜索中NER技术的探索与实践
- 下一篇: python程序中断时,输出打印日志