解決方案
解析真實世界的文字時,往往需要一個正規表達式來辨識結構化項目——例如 H1 或 (AH2b+EUH3) 這類代碼——並擷取隨後出現的訊息。若寫成一行,該模式會變成一大片密集的標點符號:巢狀群組、前瞻斷言與量詞,完全沒有空間加註解,而且每次修改都得拆解整串字串。顯而易見的修正各有代價:保留單行寫法,每次編輯都仍有風險;手寫詳細模式,同一子模式會在所有出現處重複;加入正規表達式建構函式庫,則得學習新語法外加一項依賴。
做法是把模式當成程式碼來處理。作者將每個邏輯單元各定義一次——例如 [A-Z]*H 這樣的代碼片段、以加號分隔代碼的多代碼包裝,然後是完整的比對器——每個都是原始詳細字串,每行都附註解,並透過 {code} 佔位符由小模式組成大模式。由於 re.VERBOSE 會忽略空白與井號註解,每個片段都能由上而下閱讀:括號與交替變成可見的結構,而非一堆符號。由於 f-string 在建構時解析,送進 re 模組的仍是一個純粹的模式字串。
回報正好出現在單行正規表達式最痛的地方。每個片段只定義一次,因此文法變更會更新所有內嵌它的模式;片段可在串接前先用 re.findall 單獨測試;而且技術堆疊沒有新增任何東西——作者用了兩項長期並存的標準函式庫功能,並坦承他花了三年才看出這個組合。文章記錄了陷阱:井號與空格需要轉義或改用字元類別,大括號量詞在 f-string 內必須加倍,而內嵌的 (?x) 旗標可在呼叫端控制旗標時讓詳細模式繼續運作。
達成了什麼
像程式碼一樣組織正規表達式:將每個片段定義為帶註解的詳細字串一次,並以 f-string 組成最終模式
為何有效
re.VERBOSE 本來就會忽略空白與註解,因此每個片段讀起來就像有文件說明的程式碼,且只使用標準函式庫。
F-string 插值在建構時接合片段,留下任何 re 呼叫都能原樣接受的純粹模式字串。
每個片段只定義一次,因此文法變更會更新每個組成的模式,而非重複的內嵌副本。
片段可在組成前單獨測試,因此失敗會定位到片段之間的接合點。
零依賴且沒有新 DSL,意味著此法可轉移到任何 Python 3.6+ 程式碼庫,無需額外設定。
可應用之處
當某個模式難以維護時,別加入函式庫:結合你已有的功能——用詳細註解提升可讀性、用插值實現重用——且零依賴。
後續
該文於 2022 年 5 月發表,在 Hacker News 獲得 304 分與 90 則留言;反應分為兩派:一派視該文為啟示,另一派指出將正規表達式拆成可重用子模式早已是常見做法。留言者指出更強的機制——PCRE 與 Perl 的 DEFINE 副程式區塊,以及第三方 regex 模組——可在不靠插值的情況下重用,作者也追加了 bonus 章節,展示以 DEFINE 撰寫的同一解析器。該文仍上線,這些注意事項已有記錄,核心主張不變:零依賴、可組合且帶註解的模式。
追溯依據
參考來源
- The unreasonable effectiveness of f-strings and re.VERBOSE death.andgravity.com · death.andgravity.com
- The unreasonable effectiveness of f-strings and re.verbose — Hacker News discussion Hacker News · news.ycombinator.com ·