快轉到主要內容
  1. 教學文章/

Python match/case:結構化模式匹配完全攻略

·6 分鐘· loading · loading · ·
Python Match Pattern-Matching Python 3.10
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
Python 學習 - 本文屬於一個選集。
§ 36: 本文

一、前言
#

你有沒有寫過一長串 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 重構看看——你會愛上那種清爽感 😎

延伸閱讀
#

Python 學習 - 本文屬於一個選集。
§ 36: 本文

相關文章

Python enum:打造型別安全的常數管理
·5 分鐘· loading · loading
Python Enum 型別安全 設計模式
Python contextlib:掌握 Context Manager 的進階魔法
·7 分鐘· loading · loading
Python Contextlib Context Manager With 資源管理
sqlite3:Python 內建輕量資料庫完全攻略
·9 分鐘· loading · loading
Python Sqlite3 SQL 資料庫 Database
pathlib:優雅處理檔案路徑的現代方式
·6 分鐘· loading · loading
Python Pathlib 檔案處理 標準庫
httpx:Python 新世代 HTTP 客戶端完全攻略
·4 分鐘· loading · loading
Python Httpx HTTP Async Requests
Python collections 模組:讓你的資料結構更強大
·5 分鐘· loading · loading
Python Collections Counter Defaultdict Deque Namedtuple