當前位置:
首頁 > 知識 > Python 的正則表達式彩蛋

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實踐
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丨我們如何表達愛。