match 语句会把一个值依次与一系列*模式(pattern)*比较,并执行第一个匹配成功的分支。 match 远不只是一个 switch,因为模式可以检查值的结构, 深入查看内部内容,并一次性提取出需要的部分。 任何 C 风格 switch 都无法表达下面这种操作:

match event:
    case {"type": "click", "x": x, "y": y}:
        ...

这个 case 只匹配一种字典:它的 "type" 必须是 "click", 而且在匹配的同时,会从该字典中绑定 x 和 y。 当模式不只是做相等性比较时,match 的价值才真正体现出来。

模式匹配最早在控制流中出现过。

match 和 case 都是软关键字(soft keyword): 只有在这种语句内部,它们才充当关键字, 所以已有代码如果把 match 当变量名,仍然可以运行。 这同时也是你不该写这种代码的理由。 一个名叫 match 的局部变量不会遮蔽什么真正的名字,但读起来会像 match 语句。

匹配值

最简单的模式是字面量值。 最后的 case _ 是通配符。 它会匹配任何内容,作用类似 default。 如果没有它,而某个 match 没有任何模式匹配成功,那么什么也不会发生,也不会抛出错误。 match 会从上到下依次尝试模式,第一个匹配成功的模式获胜: 执行一个 case 的函数体,然后整个语句结束。 与 C 的 switch 不同,各个 case 不会 fall through, match 里也没有可能被你忘记的 break:

# http_status.py

def describe(status: int) -> str:
    match status:
        case 200:
            return "OK"
        case 404:
            return "Not Found"
        case 500:
            return "Server Error"
        case _:
            return f"Status{status}"

print(describe(200))
#: OK
print(describe(404))
#: Not Found
print(describe(301))
#: Status 301

字面量模式使用 == 比较,而不是 is, 所以 case 200: 也会匹配 200.0,case 1: 也会匹配 True。 None、True 和 False 是例外:这三个值使用 is 比较, 因此 case True: 不会匹配 1。

对于这种“一个值映射到另一个值”的查找,字典通常更短 (见什么时候不该用 match)。

备选模式与捕获

备选模式(alternative)使用 | 在一个 case 中组合多个模式。 同一个 | 中的每个备选模式都必须绑定同一组名字。

一个裸名字就是捕获模式(capture pattern)。 和 _ 一样,它无条件匹配任何值。 与 _ 不同的是,它还会把匹配到的值绑定到这个名字上。 这里,other 就是捕获模式:

# step.py

def step(command: str) -> str:
    match command:
        case "up" | "u":
            return "y -= 1"
        case "down" | "d":
            return "y += 1"
        case other:
            return f"unknown command:{other}"

print(step("up"))
#: y -= 1
print(step("d"))
#: y += 1
print(step("jump"))
#: unknown command: jump

裸名字永远意味着绑定。 它不会拿这个名字去和同名变量做比较, 所以如果你在 case 里写一个具名常量,它会悄悄变成捕获。 *值模式(value pattern)*必须是带点号的名字,它才会真正执行比较。 Signal 是一个 Enum (将数据类作为类型中介绍过枚举):

# value_patterns.py
from enum import Enum
from typing import Final

class Signal(Enum):
    STOP = "stop"
    GO = "go"

DEFAULT: Final[Signal] = Signal.STOP

def act(s: Signal) -> str:
    match s:
        case Signal.GO:
            return "accelerate"
        case Signal.STOP:
            return "brake"

def broken(s: Signal) -> str:
    match s:
        case DEFAULT:
            return f"DEFAULT is now{DEFAULT}"
    return "unreachable"

print(act(Signal.GO), act(Signal.STOP))
#: accelerate brake
print(broken(Signal.GO))
#: DEFAULT is now Signal.GO
print(DEFAULT)
#: Signal.STOP

case Signal.GO 会比较值。 case DEFAULT 则会绑定:它会匹配 Signal.GO, 把 DEFAULT 重新绑定成 broken() 内部的局部名字, 模块级的那个常量保持不变。 如果一个裸名字捕获之后还有后续 case,Python 会发现这个错误, 并拒绝编译:SyntaxError: name capture 'DEFAULT' makes remaining patterns unreachable。 但如果捕获模式正好是最后一个 case,像这里这样,Python 就不会警告你。 ruff 能注意到这一点:它会按 N806 规则标记 DEFAULT (函数内部给大写名字赋值): 因为这里的 DEFAULT 实际上是一个局部变量,而不是你本来想拿来比较的常量。

act() 还展示了为什么值得费力使用 enum:Signal 是一个封闭集合, 所以类型检查器能看出这些 case 已经覆盖两个成员,也不会抱怨缺少 return。