跳到主要内容

模式匹配

当程序需要根据数据的结构做不同处理时,match ... case 比一长串 if/elif 更直观。例如,命令可能是字符串,消息可能是字典,坐标可能是元组。模式匹配可以同时判断形状、提取数据和增加附加条件。

本文使用 Python 3.10 及以上版本。旧版本没有 match 语法,应改用 if/elif 或升级 Python。

基本语法​

command = "start"

match command:
case "start":
print("开始任务")
case "stop":
print("停止任务")
case _:
print("未知命令")

match 依次尝试每个 case,执行第一个匹配的分支后结束。下划线 _ 是通配模式,匹配任何值但不绑定变量,通常放在最后作为默认分支。

字面量和多个选项​

字面量模式可以匹配数字、字符串、布尔值和 None:

status_code = 404

match status_code:
case 200:
message = "成功"
case 401 | 403:
message = "没有权限"
case 404:
message = "资源不存在"
case _:
message = "其他响应"

print(message)

竖线 | 表示“匹配其中任意一个模式”。同一个 case 中如果绑定变量,所有选项必须绑定相同的变量名,否则会产生语法错误。

绑定变量和守卫条件​

捕获模式把匹配到的值绑定到变量:

value = 42

match value:
case number:
print(f"收到数字:{number}")

注意:case number 会匹配几乎所有值,它不是“判断是否为名为 number 的常量”。如果要匹配特定常量,使用字面量;如果要判断类型,使用类模式。

守卫(if)可以在模式匹配后继续限制条件:

score = 86

match score:
case int(value) if value >= 90:
level = "优秀"
case int(value) if value >= 60:
level = "及格"
case int():
level = "不及格"
case _:
level = "分数格式错误"

守卫只在模式已经匹配后求值,因此可以安全地使用模式中绑定的变量。

序列模式​

列表和元组等序列可以按长度和位置匹配:

point = (10, 20)

match point:
case (0, 0):
print("原点")
case (x, y):
print(f"坐标:({x}, {y})")

使用 * 捕获任意数量的剩余元素:

values = [1, 2, 3, 4]

match values:
case [first, *middle, last]:
print(first, middle, last) # 1 [2, 3] 4

[x, y] 只匹配恰好两个元素;[x, y, *rest] 可匹配至少两个元素。字符串不是可用于这种序列模式的字符序列,不能用 case [first, *rest] 拆分字符串。

映射模式​

字典可以按键匹配并提取值:

event = {"type": "login", "user": "alice", "ip": "127.0.0.1"}

match event:
case {"type": "login", "user": user}:
print(f"用户 {user} 登录")
case {"type": "logout", "user": user}:
print(f"用户 {user} 退出")
case {"type": event_type}:
print(f"其他事件:{event_type}")
case _:
print("无效事件")

映射模式不要求字典只有这些键,示例中的额外 ip 不影响匹配。若需要捕获未列出的剩余键,可使用 **rest:

data = {"name": "小林", "age": 18, "city": "上海"}
match data:
case {"name": name, **rest}:
print(name, rest)

类模式​

类模式可以检查对象类型并读取属性。对内置类型,常见写法如下:

value = "hello"

match value:
case str(text) if text:
print(f"非空文本:{text}")
case str():
print("空文本")
case int(number):
print(f"整数:{number}")
case _:
print("其他类型")

对自定义类,需要类提供位置属性(__match_args__)或使用关键字属性模式:

class User:
def __init__(self, name, active):
self.name = name
self.active = active


user = User("小林", True)

match user:
case User(name=name, active=True):
print(f"活跃用户:{name}")
case User(name=name):
print(f"其他用户:{name}")

嵌套模式​

模式可以嵌套,适合解析结构化消息:

message = {"kind": "point", "value": [10, 20]}

match message:
case {"kind": "point", "value": [x, y]}:
print(f"点的位置是 {x}, {y}")
case {"kind": kind}:
print(f"不支持的消息类型:{kind}")
case _:
print("消息格式错误")

相比先检查键、再检查类型、最后取值,嵌套模式把数据形状集中写在一个 case 中,分支意图更明确。

模式匹配和 if/elif 的选择​

场景更合适的写法
只比较几个简单条件if/elif
判断元组、列表或字典的结构match/case
需要复杂布尔表达式if/elif 或函数
处理有限的命令、事件类型match/case
兼容 Python 3.9 及更早版本if/elif

模式匹配不是所有条件判断的替代品。不要为了使用新语法而把简单的 if 改写成复杂模式。

常见错误​

把捕获模式当成常量​

# case status: 会捕获任意值,而不是匹配变量 status 的当前值

匹配常量时直接写字符串或数字;非字面量常量可使用限定名称,例如 Color.RED。

默认模式挡住后续分支​

case _: 会匹配一切,后面的 case 永远不会执行。因此默认分支必须放在最后。

忽略类型版本​

运行 match 代码前确认解释器版本:

import sys

print(sys.version)

部署环境也要使用 Python 3.10+,否则程序会在解析阶段失败。

练习​

  1. 使用 match 根据 HTTP 状态码输出提示,并把 401 和 403 合并到一个分支。
  2. 匹配一个包含 "type" 和 "payload" 的字典,分别处理文本和数字载荷。
  3. 匹配一个坐标列表:原点、横轴上的点、普通点分别输出不同描述。
  4. 为分数添加守卫条件,区分优秀、及格和不及格。

小结​

  • match/case 按顺序匹配值的结构,case _ 是默认通配分支。
  • 字面量、|、序列模式、映射模式和类模式可以组合使用。
  • 捕获模式会绑定变量;守卫 if 可在结构匹配后增加业务条件。
  • 映射模式允许字典包含额外键,**rest 可捕获剩余键值。
  • 模式匹配需要 Python 3.10+,简单布尔条件仍可使用 if/elif。