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