Python 3.14 結構化模式匹配 × Dataclass 進階實戰:優雅取代 if-elif 的現代寫法
Python 3.10 引入的 match-case 語法在社群中的評價一直很兩極——有人覺得它是 Python 最棒的新功能,有人覺得它只是語法糖。但到了 Python 3.14,搭配 Dataclass 使用後,我必須說,它已經從「可有可無」變成了「回不去了」。特別是在處理複雜的資料結構和業務邏輯時,match-case 的可讀性甩 if-elif 好幾條街。
為什麼要用結構化模式匹配
先看一個典型的 if-elif 地獄:
def process_event(event):
if isinstance(event, dict) and event.get("type") == "click":
if "x" in event and "y" in event:
handle_click(event["x"], event["y"])
elif isinstance(event, dict) and event.get("type") == "scroll":
if "direction" in event:
handle_scroll(event["direction"])
elif isinstance(event, dict) and event.get("type") == "keypress":
if "key" in event:
handle_keypress(event["key"])
else:
handle_unknown(event)用 match-case 重寫:
def process_event(event):
match event:
case {"type": "click", "x": x, "y": y}:
handle_click(x, y)
case {"type": "scroll", "direction": d}:
handle_scroll(d)
case {"type": "keypress", "key": k}:
handle_keypress(k)
case _:
handle_unknown(event)同時做了型別檢查、結構驗證和值提取,而且一眼就能看懂邏輯。
__match_args__ 與 Dataclass 的魔法
Dataclass 之所以跟 match-case 是絕配,關鍵在於 __match_args__。當你定義一個 dataclass 時,Python 會自動根據欄位定義的順序生成 __match_args__,讓你可以在 case 中用位置參數來匹配。
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
# __match_args__ 自動被設為 ('x', 'y')
match point:
case Point(0, 0):
print("原點")
case Point(x, 0):
print(f"在 X 軸上,x={x}")
case Point(0, y):
print(f"在 Y 軸上,y={y}")
case Point(x, y):
print(f"點 ({x}, {y})")注意一個微妙的區別:Point(0, 0) 在 match 語句中是建立一個新實例,但在 case 子句中是解構模式——它不會建立新物件,而是檢查匹配目標是否符合這個結構。
基礎模式匹配模式
match-case 支援七種核心模式:
- 字面量模式:
case 42:、case "hello": - 擷取模式:
case x:(把值綁定到變數 x) - 萬用字元:
case _:(匹配任何值,不綁定) - 序列模式:
case [1, 2, *rest]: - 映射模式:
case {"key": value}: - 類別模式:
case Point(x, y): - OR 模式:
case "yes" | "y" | "ok":
這些模式可以自由組合和巢狀,打造出非常精確的匹配邏輯。
Dataclass 解構模式實戰
來看一個更實際的例子——用 dataclass + match-case 建立一個簡單的運算式求值器:
@dataclass
class BinaryOp:
op: str
left: 'Expr'
right: 'Expr'
@dataclass
class UnaryOp:
op: str
operand: 'Expr'
@dataclass
class Literal:
value: float
Expr = BinaryOp | UnaryOp | Literal
def evaluate(expr: Expr) -> float:
match expr:
case Literal(value):
return value
case UnaryOp("-", operand):
return -evaluate(operand)
case BinaryOp("+", left, right):
return evaluate(left) + evaluate(right)
case BinaryOp("-", left, right):
return evaluate(left) - evaluate(right)
case BinaryOp("*", left, right):
return evaluate(left) * evaluate(right)
case BinaryOp("/", left, right):
return evaluate(left) / evaluate(right)
case _:
raise ValueError(f"Unknown expression: {expr}")注意 BinaryOp("+", left, right) 這個模式同時做了三件事:檢查型別是 BinaryOp、檢查 op 欄位等於 "+"、把 left 和 right 解構到變數中。這就是 pattern matching 的威力——一行做了以前需要三行才能完成的事。
如果你對 Python 的進階功能有興趣,也推薦看 Python 3.14 Free Threading 完整教學,了解移除 GIL 的革命性改變。
進階模式:Guard 條件與巢狀匹配
你可以在 case 後面加上 if guard 來進一步過濾:
match point:
case Point(x, y) if x > 0 and y > 0:
print("第一象限")
case Point(x, y) if x < 0 and y > 0:
print("第二象限")巢狀匹配也很自然——你可以在一個 case 中解構多層結構:
@dataclass
class Line:
start: Point
end: Point
match line:
case Line(Point(0, 0), Point(x, y)):
print(f"從原點到 ({x}, {y})")
case Line(Point(x1, y1), Point(x2, y2)) if y1 == y2:
print(f"水平線,y={y1}")實戰案例:ETL 資料處理
pattern matching 在 ETL(Extract-Transform-Load)場景中特別好用。假設你在處理來自不同來源的使用者資料:
@dataclass
class RawUser:
source: str
data: dict
def transform_user(raw: RawUser) -> dict:
match raw:
case RawUser("google", {"sub": uid, "email": email, "name": name}):
return {"id": f"g_{uid}", "email": email, "name": name}
case RawUser("github", {"id": uid, "login": login, "email": email}):
return {"id": f"gh_{uid}", "email": email or f"{login}@users.github.com", "name": login}
case RawUser("email", {"address": email, **rest}):
return {"id": f"e_{hash(email)}", "email": email, "name": rest.get("name", email.split('@')[0])}
case _:
raise ValueError(f"Unknown source: {raw.source}")每個 case 同時驗證了來源、驗證了資料結構、提取了需要的欄位。比起等價的 if-elif 版本,可讀性好太多了。
相關資源:如果你在做資料處理專案,Python Marimo 互動式筆記本教學也是很棒的開發工具。
常見陷阱與最佳實踐
使用 match-case 時有幾個容易踩的坑:
- 擷取 vs 常數的混淆:
case float:是擷取模式(把值綁定到 float 變數),不是型別檢查!要做型別檢查必須用case float():。這個規則適用於 9 種內建型別 - 順序很重要:跟 if-elif 一樣,case 是按順序匹配的。把更具體的模式放在前面,通用的放在後面
- mypy 不會警告遺漏:不像 Rust 或 Haskell,Python 的型別檢查器目前還不會在你遺漏某個 case 時發出警告。建議在最後加上
case _: raise AssertionError("Unreachable")來捕捉邏輯漏洞 - init=False 的欄位:如果 dataclass 的某個欄位設定
init=False,它不會出現在__match_args__中,所以不能用位置參數匹配
結語
Python 3.14 的結構化模式匹配搭配 Dataclass,提供了一種更聲明式的方式來處理複雜的資料結構和分支邏輯。它不只是 if-elif 的語法糖——它根本改變了你思考資料處理邏輯的方式。下次當你寫出超過 5 層的 if-elif 鏈時,試著用 match-case 重寫一次,你可能會驚喜地發現程式碼變得多麼清晰。建議從 ETL 或事件處理這類場景開始嘗試,這是 pattern matching 最能發揮威力的地方。
繼續閱讀
Python uv 專案管理完整教學:取代 pip 與 Poetry 的新一代開發工具
uv 是 Astral 團隊用 Rust 打造的 Python 專案管理工具,速度比 pip 快 10-100 倍,能一次取代 pip、Poetry、pyenv、virtualenv 等多個工具。這篇教學從安裝到專案管理、遷移指南一次搞懂。
相關文章
你可能也喜歡
探索其他領域的精選好文