一、前言 #
你有沒有寫過一長串 if/elif/else,結果整段程式碼又臭又長,改一個條件就像拆炸彈?
# 經典的 if/elif 地獄
if isinstance(data, dict) and "type" in data and data["type"] == "user":
handle_user(data)
elif isinstance(data, dict) and "type" in data and data["type"] == "admin":
handle_admin(data)
elif isinstance(data, list) and len(data) == 2:
x, y = data
handle_point(x, y)
else:
handle_unknown(data)
Python 3.10 帶來了 match/case — 結構化模式匹配(Structural Pattern Matching),讓你用宣告式的方式描述資料的「形狀」,程式碼瞬間變得乾淨可讀。
📌 注意:這不是其他語言的 switch/case!Python 的 match 能解構、能綁定變數、能設條件守衛,威力完全不同。
今天拍拍君帶你從零開始,一路打到進階用法 🔥
二、基本語法 #
match/case 的基本結構長這樣:
def http_status(code: int) -> str:
match code:
case 200:
return "OK"
case 301:
return "Moved Permanently"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
case _:
return f"Unknown status: {code}"
幾個重點:
match後面接要匹配的值case後面接模式(pattern)_是萬用字元,匹配任何東西(相當於 default)- 不需要
break,匹配到第一個就停
print(http_status(200)) # OK
print(http_status(404)) # Not Found
print(http_status(418)) # Unknown status: 418
三、字面值模式與 OR 模式 #
字面值匹配 #
你可以匹配數字、字串、布林值、None:
def describe(value):
match value:
case True:
return "是布林 True"
case None:
return "是 None"
case 0:
return "是零"
case "hello":
return "是打招呼"
case _:
return f"其他:{value}"
⚠️ 注意順序!
True要放在整數前面,因為True == 1在 Python 中成立。
OR 模式(|)
#
用 | 合併多個模式:
def classify_day(day: str) -> str:
match day.lower():
case "saturday" | "sunday":
return "週末 🎉"
case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":
return "工作日 💼"
case _:
return "不認識的日子"
print(classify_day("Saturday")) # 週末 🎉
print(classify_day("Monday")) # 工作日 💼
四、捕獲模式與解構 #
這是 match/case 最強大的地方 — 直接把值「拆開」並綁定到變數。
序列解構 #
def handle_point(point):
match point:
case (0, 0):
return "原點"
case (x, 0):
return f"在 X 軸上,x = {x}"
case (0, y):
return f"在 Y 軸上,y = {y}"
case (x, y):
return f"一般點 ({x}, {y})"
case _:
return "不是座標"
print(handle_point((0, 0))) # 原點
print(handle_point((3, 0))) # 在 X 軸上,x = 3
print(handle_point((0, 7))) # 在 Y 軸上,y = 7
print(handle_point((2, 5))) # 一般點 (2, 5)
🧠 注意:
(x, 0)中的x是捕獲變數(被綁定),而0是字面值(被匹配)。
星號解構(*)
#
用 *rest 捕獲剩餘元素:
def first_and_rest(items):
match items:
case []:
return "空串列"
case [only]:
return f"只有一個:{only}"
case [first, *rest]:
return f"第一個是 {first},剩下 {len(rest)} 個"
print(first_and_rest([])) # 空串列
print(first_and_rest([42])) # 只有一個:42
print(first_and_rest([1, 2, 3])) # 第一個是 1,剩下 2 個
五、字典模式匹配 #
match/case 也能匹配字典的「形狀」——這在處理 API 回應、JSON 資料時超好用:
def handle_event(event: dict):
match event:
case {"type": "click", "x": x, "y": y}:
return f"點擊事件:({x}, {y})"
case {"type": "keypress", "key": key}:
return f"按鍵事件:{key}"
case {"type": "scroll", "direction": "up" | "down" as direction}:
return f"捲動事件:{direction}"
case {"type": t}:
return f"未知事件類型:{t}"
case _:
return "不是事件"
print(handle_event({"type": "click", "x": 100, "y": 200}))
# 點擊事件:(100, 200)
print(handle_event({"type": "keypress", "key": "Enter"}))
# 按鍵事件:Enter
print(handle_event({"type": "scroll", "direction": "up", "speed": 3}))
# 捲動事件:up (多餘的 key 不影響匹配!)
💡 字典模式是部分匹配:只要指定的 key 存在就成功,多的 key 會被忽略。
六、類別模式匹配 #
你可以用 match/case 匹配 class 的屬性,搭配 dataclass 特別方便:
from dataclasses import dataclass
@dataclass
class ChatMessage:
sender: str
content: str
is_bot: bool = False
@dataclass
class SystemNotice:
content: str
level: str = "info"
def render_message(msg):
match msg:
case ChatMessage(sender="拍拍君", content=text):
return f"🤖 拍拍君說:{text}"
case ChatMessage(sender=name, content=text, is_bot=True):
return f"🤖 {name}(bot):{text}"
case ChatMessage(sender=name, content=text):
return f"💬 {name}:{text}"
case SystemNotice(content=text, level="error"):
return f"🚨 系統錯誤:{text}"
case SystemNotice(content=text):
return f"ℹ️ 系統通知:{text}"
case _:
return "無法辨識的訊息"
print(render_message(ChatMessage("拍拍君", "大家好!")))
# 🤖 拍拍君說:大家好!
print(render_message(ChatMessage("chatPTT", "嗨", is_bot=True)))
# 🤖 chatPTT(bot):嗨
print(render_message(SystemNotice("磁碟空間不足", level="error")))
# 🚨 系統錯誤:磁碟空間不足
📘
dataclass預設支援位置參數匹配。一般 class 需要設定__match_args__。
七、Guard 條件守衛 #
用 if 加上額外條件:
def categorize_score(score: int) -> str:
match score:
case n if n >= 90:
return "A 優秀 🏆"
case n if n >= 80:
return "B 良好 👍"
case n if n >= 70:
return "C 普通"
case n if n >= 60:
return "D 及格"
case _:
return "F 不及格 😢"
print(categorize_score(95)) # A 優秀 🏆
print(categorize_score(73)) # C 普通
print(categorize_score(42)) # F 不及格 😢
Guard 也能跟解構一起用:
def validate_config(config: dict):
match config:
case {"workers": n} if n > 32:
return "警告:worker 數量過多"
case {"workers": n} if n < 1:
return "錯誤:至少需要 1 個 worker"
case {"workers": n}:
return f"設定 {n} 個 worker ✅"
case _:
return "缺少 workers 設定"
八、實戰:命令列解析器 #
來看一個綜合範例 — 用 match/case 打造簡單的命令解析器:
def execute_command(command: str):
parts = command.strip().split()
match parts:
case ["quit" | "exit"]:
return "👋 再見!"
case ["help"]:
return "可用指令:help, greet, add, list, quit"
case ["greet", name]:
return f"你好,{name}!歡迎來到拍拍的世界 🎉"
case ["greet"]:
return "用法:greet <名字>"
case ["add", *numbers] if all(n.isdigit() for n in numbers):
total = sum(int(n) for n in numbers)
return f"加總結果:{total}"
case ["add", *_]:
return "錯誤:add 後面只能接數字"
case ["list", "files", directory]:
return f"列出 {directory} 的檔案..."
case ["list", "files"]:
return "列出當前目錄的檔案..."
case [cmd, *_]:
return f"未知指令:{cmd}"
case []:
return "請輸入指令"
print(execute_command("greet 拍拍醬")) # 你好,拍拍醬!歡迎來到拍拍的世界 🎉
print(execute_command("add 1 2 3")) # 加總結果:6
print(execute_command("exit")) # 👋 再見!
print(execute_command("add a b")) # 錯誤:add 後面只能接數字
print(execute_command("list files /tmp")) # 列出 /tmp 的檔案...
九、常見陷阱 #
陷阱 1:變數名會被捕獲,不會被匹配 #
HTTP_OK = 200
def check(code):
match code:
case HTTP_OK: # ⚠️ 這不是比較!這是捕獲到新變數 HTTP_OK
return "OK"
解法: 用帶點的名稱(qualified name)或 guard:
import http
def check(code):
match code:
case http.HTTPStatus.OK.value: # ✅ 帶點的名稱會比較
return "OK"
# 或者
case code if code == 200: # ✅ 用 guard
return "OK"
陷阱 2:忘記 _ 兜底
#
沒有 case _ 不會報錯,但如果都沒匹配到就什麼也不做 — 可能是 bug 的來源。
# 好習慣:永遠加上 case _
match value:
case "a":
...
case "b":
...
case _:
raise ValueError(f"Unexpected: {value}")
陷阱 3:case (x, y) vs case [x, y]
#
兩者都能匹配序列(tuple 和 list 都行),但語意上用 [] 暗示 list、() 暗示 tuple,增加可讀性。
十、match/case vs if/elif 怎麼選? #
| 場景 | 推薦 |
|---|---|
| 簡單的值比較(2-3 個分支) | if/elif |
| 複雜的資料解構 | match/case ✅ |
| 巢狀字典/物件判斷 | match/case ✅ |
| 需要 Python < 3.10 支援 | if/elif(沒選擇 😅) |
| 型別 + 欄位同時判斷 | match/case ✅ |
經驗法則: 當你在判斷「資料長什麼樣」而不是「值等於什麼」時,match/case 幾乎都是更好的選擇。
結語 #
match/case 是 Python 3.10 最令人興奮的新功能之一。它不只是語法糖 — 結構化模式匹配讓你用宣告式的方式描述資料的形狀,比起一堆 isinstance + if 檢查,程式碼更短、更清晰、更不容易出錯。
重點回顧:
- 🔹 基本匹配:字面值、OR 模式
- 🔹 解構:序列、字典、類別
- 🔹 Guard:用
if加額外條件 - 🔹 萬用字元
_:永遠記得加兜底 - 🔹 注意陷阱:變數捕獲 vs 值比較
下次碰到一大坨 if/elif,先想想能不能用 match/case 重構看看——你會愛上那種清爽感 😎