导读:每次 Python 版本迭代,亮眼的核心功能总能收获眼球的关注,而不少实用性极强的细节更新往往被忽略。本文避开热门焦点,聚焦 Python 3.15 里低调却实用的隐性升级,涵盖异步任务处理、上下文管理器、多线程迭代、数据运算与 JSON 格式优化等内容,细数这些小众特性如何解决开发痛点、完善语言能力。
又到了一年一度的新时刻,全新版本的 Python 即将面世。
随着 Python 3.15.0b1 进入功能冻结阶段,我们已经能窥见今年 Python 的更新全貌。此次版本包含多项重磅特性,其中就包括我此前介绍过的惰性导入(lazy imports)与超光速分析器(tachyon profiler)。
在以前,我们曾深入探究了 Python 3.14 的小众特性,并且乐在其中。我发现,这些特性与那些备受瞩目的 PEP 提案同样精彩,值得更多关注。今年亦是如此。
本次版本中,异步 I/O(Asyncio)模块的改动不多,核心更新是优雅取消任务组(TaskGroup) 的能力。
任务组是结构化并发的一种实现,让开发者能以简洁方式创建多个并发任务。
asyncwith asyncio.TaskGroup()as tg:
tg.create_task(run())
tg.create_task(run())
# 等待所有任务执行完毕
假设我们需要在后台等待某种信号,以中断任务组执行 —— 在异步 I/O 中,这看似简单,实际实现却颇为繁琐。
class Interrupt(Exception):
pass
with suppress(Interrupt):
asyncwith asyncio.TaskGroup()as tg:
tg.create_task(run())
tg.create_task(run())
ifawait wait_for_signal():
raise Interrupt()
上述代码能够生效,是因为任务组内抛出的异常会触发其他任务取消。自定义Interrupt异常会被封装在异常组(ExceptionGroup)中,再由contextlib.suppress捕获过滤,最终实现优雅退出。
suppress对异常组的兼容处理,本身就是 Python 3.12 中一项被忽视的特性。
撰写本文时,我也是偶然发现到的这一点。
Python 3.15 新增的TaskGroup.cancel方法,将这一过程大幅简化:
asyncwith asyncio.TaskGroup()as tg:
tg.create_task(run())
tg.create_task(run())
ifawait wait_for_signal():
tg.cancel()
相较以往,如今的写法简洁至极,无需过多解释 —— 直接取消整个任务组,且不抛出任何异常。
装饰器的编写难度颇高,甚至已经成为面试高频的考点。但你是否知道,上下文管理器也可直接作为装饰器使用?
from contextlib import contextmanager
import time
from typing import Iterator
@contextmanager
def duration(message:str)-> Iterator[None]:
start = time.perf_counter()
try:
yield
finally:
print(f"{message} 耗时 {time.perf_counter()- start:.2f} 秒")
以上是常用的上下文管理器,用于打印代码块执行时长。自 Python 3.3 起,它可直接作为装饰器:
@duration("任务执行")
def workload():
# 业务逻辑
# 也可直接作为包装器调用
duration("其他任务")(other_workload)
这种用法虽然便捷,但存在局限性,无法适用于迭代器、异步函数与异步迭代器:
@duration("异步任务")
asyncdefasync_workload():
# 异步业务逻辑
@duration("生成器任务")
def workload():
while True:
yield
原因在于,这类对象与普通函数语义不同:调用后会立即返回生成器、协程函数或异步生成器对象,导致装饰器执行瞬间结束,无法覆盖整个函数生命周期。
这一问题困扰了我许久,普通装饰器也常存在类似问题。Python 3.15 对此做出优化:ContextDecorator会自动检测被装饰函数类型,确保装饰器覆盖完整生命周期。
在我看来,这让上下文管理器成为编写装饰器的最佳方式—— 规避常见陷阱,语法更简洁。强烈推荐大家尝试这种用法。
迭代器是现代 Python 的核心基础,它将数据源与数据消费逻辑解耦,实现简洁抽象:
from typing import Iterator
def stream_events()-> Iterator[str]:
whileTrue:
yield blocking_get_event()
events = stream_events()
for event in events:
consume(event)
但在多线程 / 自由线程场景下,这一抽象会失效:迭代器默认非线程安全,可能出现数据丢失、内部状态错乱等问题。
Python 3.15 通过threading.serialize_iterator解决该问题,直接包裹原迭代器即可:
import threading
from concurrent.futures import ThreadPoolExecutor
events = threading.serialize_iterator(stream_events())
with ThreadPoolExecutor()as executor:
fut1 = executor.submit(consume, events)
fut2 = executor.submit(consume, events)
此外,新增threading.synchronized_iterator装饰器,可直接作用于生成器函数,自动包裹迭代器结果。
同时提供threading.concurrent_tee,与原生tee不同,它会为多个迭代器复制完整数据流,而非拆分数据:
source1, source2 = threading.concurrent_tee(squares(10), n=2)
with ThreadPoolExecutor()as executor:
fut1 = executor.submit(consume, source1)
fut2 = executor.submit(consume, source2)
此前,多线程环境下需依赖队列(Queue)同步数据消费;如今新增工具,无需修改原有抽象,即可适配多线程场景。
以往时刻我仅介绍 3 项小众特性,今年值得关注的更新更多。以下 2 项虽影响范围有限,但同样趣味十足。
collections.Counter是实用工具类,用于统计离散元素频次,行为类似dict[键类型, 整数],并内置丰富运算:
from collections import Counter
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
print(f"{c + d =}")# 合并计数:对应键值相加
print(f"{c - d =}")# 差值计数:仅保留正数
输出:
c + d = Counter(a=4, b=3)
c - d = Counter(a=1, b=0)
同时支持集合式运算:
print(f"{c & d =}")# 交集:取对应键最小值
print(f"{c | d =}")# 并集:取对应键最大值
输出:
c & d = Counter(a=1, b=1)
c | d = Counter(a=3, b=2)
可将Counter理解为离散元素集合,上述运算等价于:
{a_0, a_1, a_2, b_0} & {a_0, b_0, b_1} == {a_0, b_0}
{a_0, a_1, a_2, b_0} | {a_0, b_0, b_1} == {a_0, a_1, a_2, b_0, b_1}
Python 3.15 新增异或运算(xor):
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
# 异或 = 并集 - 交集
c ^ d == c | d - c & d == Counter(a=3, b=2)- Counter(a=1, b=1)== Counter(a=2, b=1)
用集合表示更清晰:
{a_0, a_1, a_2, b_0} ^ {a_0, b_0, b_1} == {a_1, a_2, b_1}
我将其归入附加特性,因我从未在实际场景中对Counter使用集合运算,也难以想到异或运算的应用场景。但为了功能完整性,开发者新增了这一特性,值得肯定。
Python 3.15 新增frozendict类型,至此,所有 JSON 数据类型(数组、布尔值、浮点数、空值、字符串、对象)均支持 ** 不可变(可哈希)** 表示形式。
json.load与json.loads新增 **array_hook参数 **,与object_hook参数互补,可直接将 JSON 解析为不可变对象:
import json
from types import MappingProxyType
# 解析为不可变字典+元组
json.loads('{"a": [1, 2, 3, 4]}', array_hook=tuple, object_hook=dict)== MappingProxyType({'a':(1,2,3,4)})
相较于版本更新里备受热议的重磅功能,这些低调的优化看似波澜不惊,却精准补齐日常开发中的各类细节短板。
从异步任务管控、装饰器机制完善,到多线程并发适配、数据运算与格式处理升级,每一处改动都贴合开发者实际编码需求。
可以看到,Python 始终在兼顾性能、易用性与稳定性的道路上稳步迭代,细碎的功能打磨持续夯实语言根基,也为开发者带来更流畅稳健的编码体验。
作者:万能的大雄
参考:
https://blog.changs.co.uk/python-315-features-that-didnt-make-the-headlines.html
本篇文章为 @ 万能的大雄 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 微信公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。
请扫描二维码,使用微信支付哦。