2021 年 10 月发布的 Python 3.10,是近十年来"语法变化最大"的一个版本:PEP 634 引入了 C 系语言里常见的 match 语句,让"按结构分发"成为一等公民;PEP 604 带来联合类型,int | str 从此可以写进类型标注。如果说 3.9 是修修补补,3.10 就是大刀阔斧——match 让一串串 if/elif 有了更优雅的替代品,联合类型则让类型提示终于像人话。
1. match 语句:结构模式匹配
match 不是简单的 switch。它匹配的是"结构"而非数值:字面量、变量捕获、序列、映射、类实例都可以作为模式,还能附加守卫条件。看一个把 HTTP 状态码翻译成人类语言的例子:
def describe(status):
match status:
case 200:
return "OK"
case 404:
return "未找到"
case int(n) if n >= 500:
return f"服务器错误 {n}"
case int(n):
return f"其它状态码 {n}"
case str(s):
return f"状态字符串 {s!r}"
case _:
return "无法识别的状态"
几个要点:模式从上往下逐个尝试,命中即停,所以 case 200 必须写在 case int(n) 之前;类模式 int(n) 匹配成功会把值绑定到变量 n;守卫 if n >= 500 在匹配后追加条件;case _ 是通配符,兜底一切。常用模式见下表:
那 match 和 if/elif 怎么选?经验法则是:分支条件涉及"结构判断"(类型、形状、属性组合)时用 match,只是简单比较数值或布尔时 if 更直白。match 最大的价值是把"解构 + 分发"合并成一步,代码读起来像一张声明式的规则表。
| 模式 | 示例 | 说明 |
|---|---|---|
| 字面量 | case 404 | 精确匹配某个值 |
| 通配符 | case _ | 匹配任意值,不绑定 |
| 捕获 | case [x, y] | 匹配序列并绑定变量 |
| OR 模式 | case 1 | 2 | 多个可选值 |
| 类模式 | case Point(x=0, y=0) | 按属性匹配并解构 |
| 守卫 | case int(n) if n > 0 | 匹配后附加条件 |
2. 用类模式解构对象
类模式按属性匹配并绑定,配合 dataclass 使用尤其舒服——解构逻辑直接写在匹配里,比一连串 isinstance 加属性判断清爽得多:
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
def locate(p):
match p:
case Point(x=0, y=0):
return "原点"
case Point(x=0, y=y):
return f"在 y 轴上,y={y}"
case Point(x=x, y=y):
return f"普通点 ({x}, {y})"
case _:
return "不是 Point 对象"
print(locate(Point(0, 0))) # 原点
print(locate(Point(0, 5))) # 在 y 轴上,y=5
print(locate(Point(3, 4))) # 普通点 (3, 4)
类模式还支持按位置匹配:case Point(0, 0) 等价于 case Point(x=0, y=0),字段多时建议用关键字形式,可读性更好。另外 match 匹配的是属性值而非内存地址,两个内容相同但不同的对象也能命中,这和 == 的语义一致。
3. 序列模式与映射模式
除了标量,模式还能直接拆解序列和字典。解析命令行参数、拆分坐标对、校验配置结构,都是它们的用武之地:
def parse_command(args):
match args:
case ["quit"]:
return "退出"
case ["add", x, y]:
return f"加法: {int(x) + int(y)}"
case ["serve", port] if port.isdigit():
return f"在端口 {port} 启动服务"
case [name, *rest]:
return f"{name},还有参数 {rest}"
case _:
return "无法识别"
print(parse_command(["add", "3", "4"])) # 加法: 7
print(parse_command(["serve", "8080"])) # 在端口 8080 启动服务
["add", x, y] 是序列模式,要求列表恰好三个元素并依次绑定变量;[name, *rest] 用星号收集剩余元素,和解包语法一脉相承。映射模式则写成 case {"code": c} 的形式按键匹配字典,解析 JSON 结构时非常顺手。
4. 联合类型:int | str
PEP 604 让类型标注可以直接用 | 连接多个类型:Optional[X] 从此写成 X | None,Union[A, B] 写成 A | B。代码更短,而且无需从 typing 导入:
def parse_id(value: int | str) -> int:
if isinstance(value, str):
return int(value)
return value
def find(key: str) -> dict | None:
"""返回字典,找不到时返回 None"""
...
更妙的是,isinstance 也支持联合类型,运行时检查与类型标注第一次用上了同一套语法:
def check(x):
if isinstance(x, int | str):
print("整数或字符串")
else:
print("其它类型")
5. zip(strict=True) 与括号化上下文管理器
zip 默认会静默截断较长的可迭代对象,数据错位时很难察觉。3.10 新增 strict=True 参数,长度不一致直接抛 ValueError,批量处理成对数据时强烈建议开启:
names = ["a", "b"]
scores = [90, 85, 95]
for n, s in zip(names, scores, strict=True):
print(n, s)
# ValueError: zip() argument 2 is longer than argument 1
另外,3.10 允许用括号包住多个上下文管理器并换行书写,长的 with 语句不再需要反斜杠续行:
with (
open("a.txt") as fa,
open("b.txt") as fb,
):
print(fa.read(), fb.read())
strict 模式最适合"两条数据必须一一对应"的场景,比如把日期列表与销量列表按行配对、把日志行号与时间戳对齐。开发阶段开着 strict,数据源一旦变化,程序立刻报错,而不是悄悄产出错位的结果。
6. 其它 3.10 更新速览
除了上面的重头戏,3.10 还有一批"小而美"的更新:
- PEP 612:参数规范变量 ParamSpec,让装饰器也能保留完整的函数签名
- PEP 613:TypeAlias 显式标记类型别名,配合类型检查器更清晰
- 错误提示升级:语法错误显示更精确的位置,并给出"是不是想写 == ?"之类的建议
- dataclass 微调:字段默认值、slots 相关行为更完善
7. 如何安装 Python 3.10
pyenv install 3.10.16
pyenv global 3.10.16
python3.10 --version
python3.10 -m pip install --upgrade pip
python3.10 -m venv .venv && source .venv/bin/activate
8. 值不值得升级?
值得。3.10 是第一个让 match 走进日常的版本,联合类型更是被 3.11、3.12 一路继承的"基座语法",学会它等于为后续所有版本铺路。它目前仍在安全维护期内(官方支持到 2026 年 10 月),第三方库兼容性良好,从 3.8/3.9 升上来几乎没有破坏性改动。两个小提醒:一是 match 的模式语法细节多,序列模式、映射模式、OR 模式各有坑,初学建议先在脚本里练熟再大规模重构;二是如果你已经用 3.11+,完全不必回退,但读懂 match 依然是理解类型系统演进(如 PEP 695)的必修课。总体判断:作为生产版本,3.10 仍可用,但新项目优先选 3.11+;作为学习对象,它值得你完整过一遍。
一个容易踩的细节:模式匹配对类型敏感,case 1 不会匹配字符串 "1";调试时多打印 type(value) 确认运行时类型。联合类型虽然写着方便,但过度使用会让标注失去信息量,建议只在"确实可能是多种类型"的地方使用。
最后提醒一句时间线:3.10 的安全维护到 2026 年 10 月结束,如果你正在规划明年的版本升级,不如直接一步到位迁到 3.11 或 3.12;而 3.10 作为"语法分水岭",非常适合用来系统学习 match 与联合类型这两块基石。
💡 学习建议:拿手头一段冗长的 if/elif 链改写成 match,体会结构匹配的威力;再把代码里所有Optional[X]、Union[A, B]换成X | None、A | B。两个动作做完,3.10 的核心就吃透了。