Python 的正則表達式彩蛋
Python部落(python.freelycode.com)組織翻譯,禁止轉載,歡迎轉發。
雖然我覺得在 Python 的標準庫里的確有不少很噁心的庫,但是 re 庫肯定不屬於這種。儘管它真的有年頭沒有更新了,但是在我看來,仍不失為動態語言中最好的庫之一。
我覺得 Python 作為一種動態語言,竟然沒有對正則表達式進行原生支持,真是少見。儘管沒有提供(原生的)語法和解釋器的支持,但(這個模塊)從純 API 的角度給出了一個設計更加完善的核心系統作為補充的解決方案。然而這個方案也挺詭異的,比方說,它的解析器是用純 Python 寫的,如果你導入庫的同時去追蹤 Python 就會產生一些很詭異的結果。最後你會發現自己90%的時間都花在了 re 的支持庫上。
久經考驗
經過時間的沉澱,正則庫早已成為歷代 Python 標準庫中不可或缺的部分。 Python3 就另當別論了,我覺得除了增加了對 unicode 的支持以外,它從始至今沒什麼本質的提升。到現在,成員枚舉都是亂七八糟的(不信就去試試看,對一個正則對象用 dir() 函數能返回什麼東西)。
用了這個正則庫最大的好處就是非常穩定,任它 Python 版本更替,我自巍然不動。你的 Python 已經不是當年的 Python了,你的 re 永遠是你的 re。考慮到我寫過那許許多多的正則表達式,卻從來沒有因為 re 庫的變動而重寫,那必須是滿滿的幸福啊。
這個庫有一點我覺得設計的挺神奇的,它的構造(compiler)和解析(parser)函數是用 Python 寫的,但是匹配(matcher) 函數是用 C 寫的。這意味著如果我們願意的話,就可以將解析器的內部結構傳遞給編譯器,從而完全繞過正則表達式的解析。雖然文檔里沒寫,但事實上確實可以這麼干。
還有很多這種例子,但是在(官方)文檔中的正則部分都沒有收錄,或者沒講清楚,所以下面我就給大家演示幾個例子,讓你見識見識 Python 的正則庫到底有多炫酷。
迭代匹配
如果要說在 Python 的正則庫當中哪個特性是最大的亮點,那毫無疑問,肯定是把 matching 和 searching 兩種功能區別開。這一點上,很多其它正則表達式引擎都沒有做到。在使用 match 函數進行匹配的時候,你可以專門指定一個起始索引位置,讓它從此位置開始匹配。
也就是說,你可以這麼寫:
這在寫詞法分析的時候就非常實用,因為你可以一直用 「^」 符號來表示行首,然後只要調整後面的 pos 索引參數就可以一直匹配下去。同時,有了這個功能,我們再也不需要自己手動分割字元串來匹配了,一下就省掉大量的內存分配和字元串複製的過程(況且 Python 並不擅長這個)。
除了 match 函數, Python 還提供了 search 函數,它能自動跳過字元串頭,直到成功匹配:
空既是色
想在 Python 中使用正則表達式實現逆匹配(一個 pattern 與指定字元串不匹配)一般來說比較麻煩。假設我們要寫一個類似維基語言那樣(比如 Markdown 語言)的語法分析器。除了那些表示特定格式的語法標記,中間還有許多文本也需要我們來處理。這時我們只想匹配那些已知的標記符號,但是中間還有很多別的內容(非語法標記)也需要處理。怎麼才能跳過這些內容呢?
一種方案是編譯一組正則表達式然後放到一個列表裡,逐個去嘗試匹配。如果全部匹配失敗,就跳過當前字元(然後繼續匹配)。
這個方案既不優雅也不高效。一般來說,匹配失敗的情況越多代碼效率就越低。因為那樣就意味著我們每次只能向後跳過一個字元,而且還是用的 Python 這種解釋型的語言(來循環)。同時,這種方案靈活性也不夠好,每次只能匹配到對應的標記符號,如果還要匹配分組就只能再把這段重新擴展一下。
難道就沒什麼好辦法了嗎?我們就不能讓正則引擎直接去掃描指定的一批正則表達式嗎?
下面有意思的來了。實際上,如果我們把表達式寫成 (a | b)這種分枝條件的樣式,它就會同時搜索是否匹配 a 或者 b。所以我們可以把要匹配的所有語法標記全部這樣寫到一起,然後去匹配就好了。這麼匹配寫起來很方便,但是匹配結果你肯定一臉懵比,因為完全不知道是被那一堆表達式中的哪一個匹配成功的。
深入正則引擎
下面進入正題。在過去的差不多 15 年裡,有一個奇葩特性一直沒有寫到正則表達式的文檔當中,那就是「掃描器」。掃描器是底層的 SRE 對象的一個屬性,讓引擎在找到一個匹配結果之後能繼續向後匹配。甚至還有一個 re.Scanner 的類(也沒有收到文檔中),它是在 SRE 模式掃描器之上構建的,提供了一個稍微高級一點的介面。
re 庫里這個掃描器雖然並不能幫助逆匹配變快,但是通過查看它的原代碼能讓我們了解到,它是怎麼基於 SRE 來實現的。
它的工作原理是先接收一個正則表達式和回調元組列表,每次匹配成功就調用回調函數,返回 match 對象,最後生成一個結果列表。如果進一步查看實現細節,就會發現它其實會手動在內部創建 SRE 模式和子模式對象。(就是說,它構造了一個大型的正則表達式而不必進行解析)。現在有了這些知識,我們就可以這樣擴展了:
這段代碼怎麼用呢?照下面這樣寫:
這裡如果沒有匹配到任何內容會拋出一個 EOFError ,如果你設置 skip = True 的話它就可以跳過未匹配的部分,用它來設計一個像維基語法分析器這種東西真是再完美不過了。
查找空位
匹配搜索時被跳過的部分我們可以用 match.start() 和 match.end() 來確定跳過部分的起止位置。那麼,之前第一個例子經過調整,就變成這樣:
解決分組問題
還有一件頭疼的事情,我們的組索引並不是正則表達式的索引,而是組合索引。這就意味著如果你的條件是像 (a | b) 這種格式,當你打算通過索引訪問這個組的時候會出問題。這還需要我們做一些額外的工作把 SRE 匹配對象用一個類包裝起來,讓它能和組索引以及組名稱相統一。如果你有興趣,我在 github 里還做了一個比上面的解決方案更複雜的版本,基本實現了包裝的效果,而且還準備了一些示例供你參考。
英文原文:http://lucumr.pocoo.org/2015/11/18/pythons-hidden-re-gems/
譯者:WDatou
※每月好書:? 深入淺出深度學習:原理剖析與Python實踐
※Python到底是不是數據分析最好的語言?聽聽這位大神怎麼說...
※SlopOne推薦演算法(附Python源碼)
※蝸牛般的 Python 深拷貝:為了 1% 情形,犧牲 99% 情形下的性能
TAG:Python |
※Python 正則表達式
※Python中的正則表達式
※Python爬蟲之正則表達式
※Python 模式匹配與正則表達式
※Python 正則表達式(分組)
※Perl 正則表達式
※Python正則表達式的7個使用典範
※Scala 正則表達式
※Python正則表達式語法補充
※MongoDB 正則表達式
※正則表達式和 Cookie使用
※不要在Python中編寫 lambda 表達式了
※Diabetes:新研究發現調節leptin表達的lncRNA
※旨在表達自我!NIKE x Patta全新膠囊系列Lookbook發布!
※lambda表達式foreach性能分析
※無法用言語表達的愛!The Void/IF I Were a cat/The Colour Monster
※Adidas Sobakov 一款旨在表達足球圈熱情的街頭經典鞋 | Xsneaker
※Being in the World——淺談Olafur Eliasson作品在建築、景觀、及城市設計視角下的表達
※[python] 常用正則表達式爬取網頁信息及分析HTML標籤總結
※Keep Talking丨我們如何表達愛。