Python 3.15: 探索那些不为人知的新特性"}],
虽然像 Python 3.15 这样的大版本发布通常会因 lazy imports 和 Tachyon profiler 等备受瞩目的特性而占据话题中心,但语言更新的真正效用往往在于那些更小、更渐进的改进。这些“不为人知”的特性经常能解决开发者体验中长期存在的摩擦点,提供更简洁的语法和更安全的并发模式。
通过优雅的任务组取消机制增强 asyncio
Python 中的结构化并发主要通过 asyncio.TaskGroup 来处理,它允许开发者将多个并发任务作为一个单一单元进行管理。然而,中断这些任务组在历史上一直非常繁琐。此前,触发优雅退出的唯一方法是在组内抛出自定义异常,然后由 ExceptionGroup 捕获并使用 contextlib.suppress 进行过滤。
Python 3.15 引入了 TaskGroup.cancel(),这显著简化了这一过程。开发者现在可以直接在任务组实例上调用 .cancel(),而不再需要依赖基于异常的流控制来关闭任务组。这使得在满足特定信号或条件时能够实现干净、无异常的退出,减少了后台任务管理所需的样板代码。
作为鲁棒装饰器的上下文管理器
许多开发者将上下文管理器用作装饰器——这是自 Python 3.3 以来就有的功能。然而,这种模式存在一个重大缺陷:当应用于 async 函数、生成器或 async 迭代器时,它会失效。因为这些函数在被调用时会立即返回一个生成器或协程对象,上下文管理器会在实际工作负载开始之前就已退出。
在 Python 3.15 中,ContextDecorator 已更新,能够检查其包装的函数类型。它现在可以确保装饰器覆盖被包装对象的整个生命周期,无论它是标准函数、async 函数还是生成器。这一变化有效地使上下文管理器成为实现装饰器最可靠的方式之一,因为它消除了与包装复杂函数类型相关的常见“坑”(footguns)。
开发者笔记: 一些社区成员提出了担忧,认为这种变化缺乏显式的选择性启用机制,可能会改变那些依赖于先前(尽管是有缺陷的)行为的现有代码库的行为。
线程安全迭代器与自由线程化
迭代器是 Python 数据处理的基础,但它们默认并不是线程安全的。在多线程环境中——特别是在 Python 向 free-threading 迈进的过程中——这可能导致跳过数值或内部状态损坏。
为了解决这个问题,Python 3.15 在 threading 模块中引入了几个新的工具:
threading.serialize_iterator:包装现有迭代器使其具备线程安全性。threading.synchronized_iterator:一个装饰器,可将序列化应用于生成器函数的结果。threading.concurrent_tee:itertools.tee的线程安全版本,用于在多个迭代器之间复制数值,供不同线程消费。
此前,开发者严重依赖 queue.Queue 来同步线程间的数据消费。这些新的原语允许开发者在不重写逻辑以适应基于队列的架构的情况下,维持其基于迭代器的抽象。
奖励:专门的工具与数据类型
Counter XOR 操作
collections.Counter 长期以来一直支持集合类的操作,如交集 (&) 和并集 (|)。Python 3.15 增加了对称差集运算符 (^),即 XOR。这允许开发者找到存在于两个 Counter 中但不同时存在于两者中的元素。
通过 frozendict 实现不可变 JSON 对象
随着 frozendict (PEP 814) 的引入,Python 3.15 现在提供了一种方法,可以用不可变、可哈希的形式来表示所有 JSON 类型——数组、布尔值、浮点数、null、字符串和对象。
为了方便实现这一点,json.load 和 json.loads 现在包含了一个 array_hook 参数。结合现有的 object_hook,开发者可以直接将 JSON 解析为不可变结构:
import json
from frozendict import frozendict
# 直接将 JSON 解析为不可变元组和 frozendicts
json.loads('{"a": [1, 2, 3, 4]}', array_hook=tuple, object_hook=frozendict)
# 结果: frozendict({'a': (1, 2, 3, 4)})
这些新增功能共同标志着向更鲁棒的并发和更灵活的数据处理的转变,确保 Python 保持在高性能、多线程应用领域的竞争力。