Python 正則表示法:零寬斷言實戰指南 (?=pattern) 正向先行 ; (?!pattern) 負向先行 ; (?<=pattern) 正向回顧 ; (?<!pattern) 負向回顧 ; (? 往後(右)看 ; (?< 往前(左)看 ; =必須符合 ; !不可符合

加入好友
加入社群
Python 正則表示法:零寬斷言實戰指南 (?=pattern) 正向先行 ; (?!pattern) 負向先行 ; (?<=pattern) 正向回顧 ; (?<!pattern) 負向回顧 ; (? 往後(右)看 ; (?< 往前(左)看 ; =必須符合 ; !不可符合 - 儲蓄保險王

核心觀念

零寬斷言只「檢查」相鄰條件,
不消耗字元(匹配位置不前進)。
常見四種(p = pattern):
(?=p) 正向先行:後面要是 p
(?!p) 負向先行:後面不可是 p
(?<=p) 正向回顧:前面要是 p(Python 要固定寬度)
(?<!p) 負向回顧:前面不可是 p(Python 要固定寬度)
下列程式可直接跑,會印出全數 PASS 讓你驗證。

因為正則引擎是由左往右匹配,先行斷言是在「當前位置往右邊」先檢查接下來的內容是否符合條件,才決定是否繼續匹配;它不消耗字元,只是「預先查看」所以叫「先行」。

先行 lookahead (?=p)/(?!p):從當前位置往右看「即將到來」的字元是否符合/不符合 p。
回顧 lookbehind (?<=p)/(?<!p):從當前位置往左看「已經過去」的字元是否符合/不符合 p。

一次驗證所有觀念的測試程式

import re

def run_tests():
    # 1) 負向先行 (?!\d):「後面不是數字」;常用來避免小數點被誤切
    filename = "14.5GHZ.csv"
    parts = re.split(r"\.(?!\d)", filename)  # 只在非小數點的點切
    assert parts == ["14.5GHZ", "csv"]

    # 用 (?!.*\.) 鎖定「最後一個點」來去掉副檔名
    def base_name(fn: str) -> str:
        # 移除「最後一個點」後面的副檔名
        return re.sub(r"\.(?!.*\.)[^.]*$", "", fn)

    assert base_name("14.5GHZ.csv") == "14.5GHZ"
    assert base_name("v1.2.3.tar.gz") == "v1.2.3.tar"
    assert base_name(".env") == ""  # 隱藏檔案特例,必要時自行保留

    # 2) \d+(?!\d):抓「不被下一個數字延伸」的整數邊界(零寬,不吃掉後字元)
    s = "id=123a 456 78b90"
    nums = re.findall(r"\d+(?!\d)", s)
    assert nums == ["123", "456", "78", "90"]

    # 對比:\d+[^\d] 會「吃掉」後面的非數字,不是我們要的邊界檢查
    bad = re.findall(r"\d+[^\d]", s)
    assert bad == ["123a", "456 ", "78b"]  # 不符合「只取數字」的需求

    # 3) 正向先行 (?=bar):匹配 foo 但要求後面緊跟 bar(不消耗 bar)
    t = "foobar fooqux foobar"
    hits = re.findall(r"foo(?=bar)", t)  # 只回傳 foo 的匹配
    assert hits == ["foo", "foo"]  # 第二個 "fooqux" 不算

    # 4) tempered dot:避免跨越 END(非貪婪 .*? 也可,但此法更穩定)
    text = "START A END B END"
    # 說法:從 START 起,重複「下一步不是 END 才吃一個字元」,直到遇到第一個 END
    m = re.search(r"START(?:(?!END).)*END", text)
    assert m and m.group(0) == "START A END"

    # 對比:貪婪 .* 會吃到最後一個 END
    m2 = re.search(r"START.*END", text)
    assert m2 and m2.group(0) == "START A END B END"

    # 5) 正向回顧 (?<=\$):抓被 $ 前綴的金額(固定寬度回顧)
    money = "Price $12.50, tax 3, amount $7"
    cash = re.findall(r"(?<=\$)\d+(?:\.\d+)?", money)
    assert cash == ["12.50", "7"]

    # 6) 邊界類需求:只想匹配完整單字 USD
    # 方案 A:\b 單字邊界(簡潔)
    txt = "USD USD1 XUSD USDX"
    a = re.findall(r"\bUSD\b", txt)
    assert a == ["USD"]

    # 方案 B:用 lookaround 模擬(更顯式):前後都不能是 \w
    b = re.findall(r"(?<!\w)USD(?!\w)", txt)
    assert b == ["USD"]

    print("ALL PASS")

if __name__ == "__main__":
    run_tests()

輸出結果:

Python 正則表示法:零寬斷言實戰指南 (?=pattern) 正向先行 ; (?!pattern) 負向先行 ; (?<=pattern) 正向回顧 ; (?<!pattern) 負向回顧 ; (? 往後(右)看 ; (?< 往前(左)看 ; =必須符合 ; !不可符合 - 儲蓄保險王

推薦hahow線上學習python: https://igrape.net/30afN

  • 橫軸:= / !
    • = 表示「要符合」
    • ! 表示「不要符合」
  • 縱軸:? / ?<
    • ? 表示「往後看」
    • ?< 表示「往前看」

這樣就會變成:

Python 正則表示法:零寬斷言實戰指南 (?=pattern) 正向先行 ; (?!pattern) 負向先行 ; (?<=pattern) 正向回顧 ; (?<!pattern) 負向回顧 ; (? 往後(右)看 ; (?< 往前(左)看 ; =必須符合 ; !不可符合 - 儲蓄保險王

白話版

Python 正則表示法:零寬斷言實戰指南 (?=pattern) 正向先行 ; (?!pattern) 負向先行 ; (?<=pattern) 正向回顧 ; (?<!pattern) 負向回顧 ; (? 往後(右)看 ; (?< 往前(左)看 ; =必須符合 ; !不可符合 - 儲蓄保險王

regex 掃描文字本來就是由左到右,
所以「看後面」是預設方向,只寫成 ?;
只有「往回看前面」這種比較特別的方向,才額外寫成 ?<。
因此沒有對稱的 ?> 來表示「往右看」。

因為 regex 本來就是沿字串由左向右匹配,
所以「看右邊」(lookahead)是預設,不另外寫成 ?>;
只有「看左邊」(lookbehind)時,才加 < 變成 ?<。

右看是預設,左看才加箭頭。

有一個 regex 語法真的長得像 ?>:

(?>...)

但這不是 lookahead 的「向右箭頭版」,
它通常是 atomic group(原子群組),意思完全不同。

所以更要避免把 ?> 想成「本來應該存在但沒採用的向右 lookaround」。


一句話總結

不是因為「英文」所以沒有 ?>,
而是因為 regex 的預設匹配方向本來就是往右,
因此 lookahead 不必額外標方向;
只有往左看的 lookbehind 才需要 ?<。

這個表格背後的組合邏輯

你可以這樣記:

第一步:先決定方向

  • ?:看後面
  • ?<:看前面

第二步:再決定條件

  • =:要符合
  • !:不要符合

所以四格就自然組出來:

  • ? + = → (?=p)
  • ? + ! → (?!p)
  • ?< + = → (?<=p)
  • ?< + ! → (?<!p)
Python 正則表示法:零寬斷言實戰指南 (?=pattern) 正向先行 ; (?!pattern) 負向先行 ; (?<=pattern) 正向回顧 ; (?<!pattern) 負向回顧 ; (? 往後(右)看 ; (?< 往前(左)看 ; =必須符合 ; !不可符合 - 儲蓄保險王

其中:

  • ? = 往後看
  • ?< = 往前看
  • = = 要符合
  • ! = 不要符合

再濃縮成一句口訣

先看方向,再看肯否。

  • ? 看後面,?< 看前面
  • = 要符合,! 不可符合

推薦hahow線上學習python: https://igrape.net/30afN

加入好友
加入社群
Python 正則表示法:零寬斷言實戰指南 (?=pattern) 正向先行 ; (?!pattern) 負向先行 ; (?<=pattern) 正向回顧 ; (?<!pattern) 負向回顧 ; (? 往後(右)看 ; (?< 往前(左)看 ; =必須符合 ; !不可符合 - 儲蓄保險王

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡

You may also like...

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *