Python 3.15.0b1 进入功能冻结后,正式版大致会在年内发布。版本轮廓已经清楚,接下来更多是修 bug、稳定接口,而不是继续塞大功能。
外界更容易盯住 lazy imports 和 Tachyon profiler。一个关系启动性能,一个关系性能分析,都有 headline 味道。但我更在意另一批小改动:它们不热闹,却直接碰到异步取消、装饰器生命周期、多线程消费迭代器、不可变 JSON 解析这些老摩擦。
这类改动的价值,不在于让 Python 换一副面孔。它更像把几处“能写,但总要绕一下”的地方补平。对后端和基础库开发者来说,少一个绕法,往往就是少一个维护坑。
异步控制流:少用异常传递正常意图
asyncio.TaskGroup 是 Python 结构化并发里的重要接口。过去如果想中途停止一个任务组,常见做法是抛一个自定义异常,再用 ExceptionGroup 和 contextlib.suppress 把它过滤掉。
这套写法不是不能用。问题是语义别扭:明明是“我要正常停掉这一组任务”,代码里却表现成“发生了一个异常”。后来接手的人,还得先理解异常分组,再理解为什么这个异常其实不是错误。
Python 3.15 新增 TaskGroup.cancel 后,开发者可以直接取消任务组。这个变化不大,但方向很对:正常控制流,就不要再借异常说话。
ContextDecorator 的变化也在同一条线上。过去 context manager 当装饰器用时,包同步函数比较自然;但遇到 async 函数、生成器、异步生成器,容易只包住“创建对象”的瞬间,没包住完整执行周期。
3.15 会让 ContextDecorator 覆盖这些对象的完整生命周期。计时、追踪、资源清理、临时上下文注入这类横切逻辑,出错面会小一些。
| 改动 | 过去的摩擦 | 3.15 的变化 | 更该关注的人 |
|---|---|---|---|
| TaskGroup.cancel | 取消任务组常借自定义异常和 ExceptionGroup 过滤 | 可直接取消任务组 | 写 asyncio 服务、后台任务、超时退出逻辑的人 |
| ContextDecorator 扩展 | async / generator 场景生命周期覆盖不完整 | 覆盖异步函数、生成器、异步生成器完整生命周期 | 写 tracing、计时器、资源管理装饰器的人 |
| threading.serialize_iterator 等 | 多线程消费迭代器常要改成 Queue 或手写锁 | 显式包装迭代器来同步访问 | 写基础库、数据管道、并发工具的人 |
| json array_hook | object_hook 能处理对象,数组缺少对称入口 | 数组可转 tuple,对象可配合 frozendict | 写配置解析、缓存键、不可变数据模型的人 |
如果团队里已经有一批自定义装饰器、asyncio 任务编排工具,升级前最该做的不是“立刻改写”。而是先把这些地方列出来:哪些代码在用异常表达取消,哪些装饰器实际依赖完整生命周期。等 3.15 稳定后,再判断能不能删掉一层样板代码。
线程安全迭代器:free-threading 需要这种小零件
Python 3.13 已经开始提供 free-threaded 构建作为实验方向。它的目标,是减少 GIL 对多线程 CPU 并行的限制。
这条路一旦继续走,很多过去被 GIL 间接遮住的问题会冒出来。迭代器线程安全就是其中之一。
Python 3.15 里的 threading.serialize_iterator、synchronized_iterator 和 concurrent_tee,解决的是多线程消费迭代器时的同步问题。serialize_iterator 用包装器串行化访问,synchronized_iterator 更适合装饰生成器函数,concurrent_tee 则让多个消费者共享同一个来源。
边界要说清楚:这不等于 Python 迭代器从此默认线程安全。开发者仍然要显式包装,或者使用对应装饰器。
这点很重要。否则很容易把“标准库给了工具”误读成“原有代码自动安全”。并发问题最怕这种误读。
对应用层项目,这个变化短期未必有存在感。很多服务端代码仍然会用 Queue、线程池、异步队列来组织生产者和消费者。
更早受到影响的,是基础库作者。库里如果暴露 iterator,并且可能被多线程复用,就要开始考虑两件事:文档里是否写清线程安全边界;API 是否提供同步包装入口。
这不是大开大合的并发模型更新。它更像“工欲善其事,必先利其器”里的器。free-threading 要走远,标准库就得把这些小工具补上。
Counter xor 和 JSON array_hook:补完整性,不要夸大
collections.Counter 新增 xor 运算,逻辑上对应多重集合的对称差。放在已有的加、减、交、并旁边,它让 Counter 的集合语义更完整。
但我不太买账它的高频价值。原作者也提到,很难立刻想到 Counter xor 的清晰用例。它更像补数学拼图,而不是多数业务代码马上会用上的工具。
json.load 和 json.loads 新增 array_hook,工程感更强一点。过去 object_hook 可以定制 JSON 对象的解析结果,但数组侧没有一个对称入口。现在数组可以通过 array_hook 转成 tuple,对象则可配合 object_hook=frozendict 转成 frozendict。
这样一来,解析后的 JSON 结构可以更容易变成不可变、可哈希的 Python 对象。配置快照、缓存键、测试基准数据、去重索引,都可能少写一些递归转换代码。
限制同样不能省。array_hook 没有改变 JSON 标准,也不是让 JSON 文件本身变成不可变格式。它只是改变 Python 解析结果的映射方式。
所以实际采用时,我会看两个变量。
一是 frozendict 相关支持能否被类型标注、序列化框架、配置库顺手接住。二是团队是否真的需要不可变 JSON 结构。如果只是读配置、取字段、传给业务逻辑,dict 和 list 仍然够用。
对多数普通项目,Python 3.15 的这些小功能不用追着升级。更现实的动作是:基础库和后端团队可以先标记三类代码——TaskGroup 取消逻辑、装饰器包裹 async/generator 的地方、多线程共享迭代器的地方。等正式版发布,再做小范围替换。
这也是我对 3.15 这批“小功能”的判断:它们不是颠覆,但能减少误会。Python 的工程体验,很多时候就是靠这种补缝慢慢变硬。
