我没想到 Python 3.15 会有这么大的变化
这可能是多年来最大的一次升级
每个 Python 版本都会宣称自己更快、更干净、更智能、更符合人体工学,诸如此类。
大多数时候,这些变化要么极其小众,要么在技术上令人印象深刻,但与人们实际编写 Python 的方式关系不大。
Python 3.15 给人的感觉不一样。
并不是因为每个功能都具有革命性。坦白说,有些功能早就该有了。有些像是在为之前的实验做清理工作。还有少数功能,可能只有语言爱好者才会在意。
但这是近一段时间以来,少数几个会让多项变化立即影响日常开发工作的 Python 版本之一。
仅仅是 lazy imports,就会改变大量大型 Python 应用的启动方式。
有意思的是,Python 3.15 还回滚了 Python 3.14 最大变化之一。
老实说,这很能说明 Python 的开发文化。他们发布了 incremental garbage collector,用户抱怨内存使用量,然后现在它又被撤回了。
很好。
这比假装每个实验性的 runtime 变更其实都很聪明要健康得多。
Lazy imports 早就该出现了
这可能是这个版本里最实用的功能。
Python 启动时间一直有个很奇怪的问题:导入半个生态系统的感觉,就像代码还没运行就先交了一笔税。
你导入一个 framework。这个 framework 导入六个 utility modules。它们又导入二十个更多的模块。然后你的 CLI tool 花两秒钟才打印出 --help。
多年来,人们一直手动绕过这个问题
if expensive_feature_enabled: import pandas
或者
def do_something(): import torch
不是因为这段代码看起来好。而是因为启动延迟很重要。
现在 Python 正式支持 lazy imports。
模块可以在真正被使用时才加载,而不是在启动期间加载。更重要的是,现有 imports 通常可以变成 lazy,而不用把整个 codebase 重写成某种被诅咒的依赖迷宫。
这比语法本身更重要。
这里真正的价值在于
如果你的代码本来就行为正常,负面影响也出奇地少。
不过说实话:有些项目会发现,它们对 import side effects 的依赖比自己以为的更多。
Python 开发者喜欢假装 imports 只是声明。很多 imports 实际上是戴着假眼镜的 initialization scripts。
JIT 终于开始在 benchmark 中变得可见
Python 在 3.13 中引入了 JIT。
当时的反应大多是
“好吧。不错。然后呢。”
因为性能提升很小,而且不稳定。
这不一定是失败。最初几个版本显然是在做基础设施工作。但人们听到 “JIT compiler” 时,会期待某种戏剧性的变化。
Python 3.15 是第一个让改进开始看起来真实的版本。
据报告,根据 workload 和 platform 的不同,geometric mean 改进大约在 8%–13%。
不,这并不会突然把 Python 变成 Rust。
但在一门使用如此广泛的语言中,自动获得 10% 的速度提升也绝不算小。
尤其是因为大多数 Python 性能优化通常意味着
用 C 重写 hot paths
把一切 vectorize 到 NumPy
到处添加 caching layers
假装 asyncio 解决了架构问题
如果 CPython 能继续让普通 Python 变快,而不要求生态系统重写,那就很重要。
有意思的是,JIT 的改进并不是某一个单一技巧。它是一组 runtime optimizations
基本上都是大多数 Python 用户永远不会想到的底层工程,除非他们不小心打开了 CPython internals。
开发者会立刻注意到吗?取决于 workload。
但与早期版本不同的是,这不再感觉纯粹停留在理论层面。
frozendict 是那种人们争论了很久的新增功能
Python 几乎从不添加新的 built-in data structures。
所以一旦添加,通常意味着生态系统已经用糟糕的方式把同样的东西重复实现了一千遍。
这基本上就是 frozendict 的故事。
Immutable dictionaries 早就存在于各种 library 中。人们用 tuples 绕过这个问题。人们包装 dictionaries。人们使用 custom classes。
现在终于有了官方版本。
config = frozendict({"host": "localhost", "port": 5432})
你不能修改它。它是 hashable 的。它可以安全地用作 dictionary key。
简单。
老实说,这感觉不像一个炫目的功能,更像是 Python 终于承认了现实。
sentinel() 修复了一个出奇烦人的模式
这是那种听起来不重要的小功能,直到你写过足够多的 Python。
很多 codebase 会这样做
MISSING = object()
为什么?因为 None 可能是一个合法值。
所以开发者会创建匿名的唯一对象作为 placeholders。
问题是这些对象很丑、不够 self documenting、对 type checking 不友好,而且整体上感觉像一种 workaround。
现在有了官方 API
NOT_SET = sentinel("NOT_SET")
它更清晰。打印效果更好。与 typing 配合得更好。
这正是 Python 通常擅长的那类语言改进。
不是革命性的。只是减少不必要的怪异之处。
新 profiler 可能比人们意识到的更重要
Python profiling 在历史上一直存在一个 trade off
你要么获得精确的 profiling,但伴随巨大的 overhead;要么获得轻量级 monitoring,但细节更少。
cProfile 基本上跟踪所有东西。这也意味着它会大幅拖慢程序。
现在 Python 3.15 添加了一个 statistical sampling profiler。
这更适合 production ish 的性能分析。
它不是跟踪每一次调用,而是周期性地采样执行情况,并估算时间花在了哪里。
精度较低。成本低得多。通常已经足够好。
老实说,很多现代 performance tooling 早就是这样做 profiling 的。Python 只是出奇地落后了一点。
而且,不,deterministic profilers 不会消失。它们在某些 debugging 场景中仍然很重要。
但对于常规优化工作,sampling profilers 通常是更现实的选择。
尤其是当开发者本来就会因为 profiling 本身改变 runtime behavior 而犹豫是否进行 profiling 时。
Error messages 继续变得没那么糟糕
过去几个版本里,Python 的 error messages 已经改善了很多。
这听起来很小,直到你把现代 Python errors 和旧版本相比,后者基本上只是对你耸耸肩。
Python 3.15 进一步改进了 suggestions。
一个有趣的例子
my_list.push(1)
Python 现在能识别出这大概是 JavaScript 脑子短路,并建议
append()
这确实有用。
老实说,现代编程已经涉及足够多的语言切换,这类错误很正常。
attribute suggestions 整体上也更聪明了。
并非开创性。但 developer ergonomics 会随着时间逐渐累积。
人们低估了有多少精力浪费在愚蠢的 debugging 摩擦上。
Comprehensions 内部的 unpacking 看起来很小,但会显著改变可读性
这个功能大概会把人分成两类
两种反应都合理。
以前,在 comprehensions 中 flatten nested iterables 看起来像这样:
[a for b in x for a in b]
技术上没问题。但并不算愉快。
现在
[*a for a in x]
这更容易扫读。
Dictionary unpacking 也可以工作
{**d for d in dicts}
这个功能本身很直接。
更大的问题是,如果人们太过聪明,Python comprehensions 本来就有变成 competitive programming puzzles 的倾向。
所以这大概是那种适度使用时很棒,但落到那些认为 one-liners 是人格特质的开发者手里就会很糟糕的功能。
Garbage collector rollback 实际上是个好信号
从哲学层面看,这可能是这个版本最有意思的部分。
Python 3.14 引入了一个 incremental garbage collector,目标是减少 pause times。
目标合理。
然后用户开始报告内存使用量增加。有时增加得非常夸张。
Python 3.15 回滚了这个变化。
老实说,这没问题。
一个会导致 memory bloat 的 runtime optimization,并不会仅仅因为实现很复杂就自动值得保留。
在某些生态系统中,人们倾向于永远捍卫每个性能实验,因为承认回头看起来很尴尬。
Python 选择撤回这个变化,而不是假装用户是在想象问题,这更健康。
旧的 generational garbage collector 暂时回归。incremental collector 可能会在更多工作之后再回来。
这大概是正确的结果。
Type system 改进继续扩展 Python 的双重人格
Python typing 继续演化成自己的平行宇宙。
有些开发者喜欢这一点。有些人忍受它。有些人仍然把 type hints 当成装饰性注释。
Python 3.15 添加了更多 TypedDict controls,比如
这让 dictionary schemas 更严格,也更具表达力。
还有 TypeForm,它有助于表示 evaluated type expressions。
有用吗?有。
令人兴奋吗?取决于你花多少时间和 static analysis tools 较劲。
现实是,Python 现在同时服务于两个非常不同的群体
有时这门语言能很好地处理这种张力。有时它又感觉像两门共享语法的语言。
Python 3.15 感觉异常务实
这大概是最主要的一点。
很多版本都有令人印象深刻的内部工作,但普通开发者几乎注意不到。
Python 3.15 包含了几项会直接影响以下方面的变化
startup performance
profiling
runtime speed
error debugging
data modeling
code readability
并不是每个功能同等重要。有些很小众。有些会在 Reddit 上引发六个月的争论。
但这个版本感觉扎根于真实的开发者行为,而不是语言理论。
人们抱怨 startup times。Python 添加了 lazy imports。 人们想要 immutable dictionaries。Python 添加了一个。 人们报告 GC memory problems。Python 回滚了这个变化。
这个 feedback loop 可能是这里最让人安心的地方。
我仍然认为有些 Python 应用正在变得荒谬地过度工程化。尤其是 AI 生态系统,似乎下定决心在打印单个 token 之前导入整个星球。
但至少 CPython 本身似乎越来越专注于实际痛点,而不是假装 developer ergonomics 和 runtime behavior 是两个互不相关的话题。
等 3.15 完整发布并接触真实项目后,我们会看到其中有多少能经受住考验。
有些功能会悄悄变成常态。有些会立刻被滥用。
有些可能会在未来版本中消失。
这也很 Python。