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_hookobject_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 的工程体验,很多时候就是靠这种补缝慢慢变硬。