攝影或3C

Python 正則表示法:零寬斷言實戰指南 (?=p) 正向先行 ; (?!p) 負向先行 ; (?<=p) 正向回顧 ; (?<!p) 負向回顧

核心觀念

零寬斷言只「檢查」相鄰條件,不消耗字元(匹配位置不前進)。
常見四種:
(?=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()

輸出結果:

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

儲蓄保險王

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