模式匹配
当程序需要根据数据的结构做不同处理时,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+,否则程序会在解析阶段失败。
练习
- 使用
match根据 HTTP 状态码输出提示,并把 401 和 403 合并到一个分支。 - 匹配一个包含
"type"和"payload"的字典,分别处理文本和数字载荷。 - 匹配一个坐标列表:原点、横轴上的点、普通点分别输出不同描述。
- 为分数添加守卫条件,区分优秀、及格和不及格。
小结
match/case按顺序匹配值的结构,case _是默认通配分支。- 字面量、
|、序列模式、映射模式和类模式可以组合使用。 - 捕获模式会绑定变量;守卫
if可在结构匹配后增加业务条件。 - 映射模式允许字典包含额外键,
**rest可捕获剩余键值。 - 模式匹配需要 Python 3.10+,简单布尔条件仍可使用
if/elif。