Python 3.15.0 RC2 发布:惰性导入、JIT 与可观测性改进值得提前测试

9 月 1 日,Python 官方发布了 Python 3.15.0 RC2。这是 3.15 的最终计划候选版本,官方公告称它包含从 RC1 以来约 144 项错误修复、构建改进和文档变更,正式版计划在 2026 年 10 月 1 日发布。

候选版的价值不在于让生产环境立刻升级,而在于给库作者、应用维护者和有 C 扩展的项目留出最后一轮兼容性验证时间。从 Python 3.15 的变更来看,开发者会碰到的不只是语法增量,还包括导入时机、性能分析接口、JIT 构建条件,以及 free-threaded 构建的扩展兼容问题。

先看清 RC2 处在什么阶段

Python 官方把 3.15.0rc2 称为最终计划候选版本。从这个版本开始,候选版到正式版之间原则上只接受经过审查、且明确属于错误修复的改动。官方还说明,3.15 系列从现在起不会再发生 ABI 变化,使用 3.15.0 发行候选版构建的二进制 wheel 可以继续用于后续 3.15 版本。

这对第三方库维护者很重要。现在发布针对 3.15 的 wheel,可以帮助下游项目发现构建脚本、C 扩展、类型检查和运行时行为问题;但对业务服务来说,候选版依然是预览版本,Python 官方明确不建议把它用于生产环境。

因此,比较稳妥的做法是把 RC2 当作兼容性测试目标,而不是生产运行时。可以在 CI 中增加一个允许失败的 3.15 任务,或者在独立的预发布环境执行完整测试,把问题尽早反馈到依赖库和 CPython 的 issue tracker。

显式惰性导入,先解决启动阶段的浪费

大型 Python 应用的启动时间,常常消耗在导入链上。解释器找到模块后,要读取文件、编译字节码并执行顶层代码;而一个命令行工具、Web 服务或插件系统在某次运行中可能只会真正使用其中一小部分模块。

过去常见的处理方式是把 import 移进函数,或者通过 importlib 手动延迟加载。这些办法有效,却会让导入语句分散到业务代码里,增加阅读和维护成本。Python 3.15 通过 PEP 810 引入了显式的 lazy 导入,让导入声明仍然可以放在模块顶部,但把真正的加载推迟到名称第一次被使用时。

示例语法如下:

1
2
3
4
5
6
7
8
lazy import myapp.reporting
lazy from myapp.exporters import CsvExporter

print("服务启动")

# 第一次访问时才加载对应模块
myapp.reporting.generate()
exporter = CsvExporter()

这里的关键不是把所有导入都改成惰性,而是把可选路径和重型依赖标记出来。比如命令行工具有多个子命令时,只有用户真正执行某个子命令,才需要加载对应的数据库驱动、图像库或云服务 SDK。主进程可以更快进入参数解析和健康检查阶段。

惰性导入也会改变错误出现的时机。模块不存在、依赖缺失或顶层初始化失败等问题,可能从进程启动阶段推迟到名称第一次访问时。错误发生点更贴近真实使用路径,但启动检查不能再简单地等同于“所有依赖都已经加载成功”。

官方文档还列出了几个限制:lazy 只能出现在模块作用域,不能放在函数、类或 try/except/finally 中;星号导入和 __future__ 导入也不能惰性化。对需要兼容旧版本 Python 的项目,可以通过 __lazy_modules__ 提供模块名列表,但旧版本不会自动获得 3.15 的惰性导入语义。

在真实项目里,建议先测量再改造。重点比较冷启动时间、常驻内存、首次请求延迟和异常堆栈位置。若一个依赖几乎每次都会被访问,惰性导入可能只是把成本从启动阶段搬到了第一次请求,甚至让尾延迟变差。

统一 profiling 入口,让性能排查更容易组合

Python 3.15 新增了 profiling 包,用一个统一的命名空间组织内置性能分析工具。官方文档列出两个主要部分:profiling.tracing 提供确定性的函数调用追踪,原有的 cProfile 仍作为向后兼容的别名;profiling.sampling 则提供新的统计采样分析器 Tachyon。

确定性分析会记录函数调用和返回,适合回答“哪些函数累计耗时最多”这类问题,但对线上服务有一定侵入性。采样分析器则按时间间隔获取运行中的调用栈,通常更适合观察长时间运行的进程和生产环境中的热点。

官方文档描述的 Tachyon 支持几种不同的目标模式:可以附加到已有进程,直接运行脚本或模块,也可以对运行中的进程做一次线程栈快照。它还区分 wall-clock、CPU、GIL 持有时间和异常处理时间等分析模式。

这套接口的实际意义,是让“性能分析”从一个需要事先改代码、重启进程的动作,逐步变成可以按问题选择采样方式的工具链。例如:

1
2
3
4
5
请求变慢
-> 先用 wall-clock 采样确认是在等 I/O 还是执行 Python 代码
-> 再用 CPU 模式定位计算热点
-> 多线程场景下观察 GIL 持有时间
-> 用火焰图或 pstats 输出进一步分析

这不代表所有线上问题都能靠采样器解决。采样频率、权限、容器 PID 命名空间、符号信息和输出格式,仍然会影响结果。上线前还要确认生产镜像是否包含对应工具,以及采样数据会不会暴露请求参数、路径或业务信息。

另外,Python 3.15 把 frame pointer 在支持的平台上设为默认构建选项。这样,系统级 profiler、调试器、崩溃分析工具和 eBPF 可观测性工具更容易沿着原生调用栈回溯 Python 解释器、扩展模块和嵌入程序。

对使用 C、C++、Rust 扩展的项目,这意味着构建链不能随意丢弃 Python 提供的编译参数。只要某个原生组件没有保留等价的 frame-pointer 选项,混合 Python/原生调用栈就可能出现无法展开的断点。

JIT 继续扩大覆盖范围,但不要把它当成开关

Python 3.15 的 JIT 编译器也有较大调整。官方 What’s New 文档提到,新的 tracing frontend 能处理比 Python 3.14 更多的字节码操作和控制流,简单的 Python 对象创建、部分重载操作和生成器也进入了支持范围;优化器还加入了基础寄存器分配和更多常量传播。

这类变化的方向很明确:JIT 不再只针对少数容易优化的路径,而是尝试覆盖更广的实际 Python 代码。不过,JIT 是否带来收益仍然取决于代码形状、解释器构建方式、热路径是否足够稳定,以及程序里有多少时间消耗在 I/O、数据库和网络等待上。

Python 官方文档还说明,JIT 构建阶段使用 LLVM 21 生成相关 stencil;运行已经构建好的 Python 并不要求终端用户安装 LLVM。换句话说,部署团队首先要确认发行版或基础镜像提供的 Python 是否启用了 JIT,而不是在运行容器里盲目安装 LLVM 就认为 JIT 已经生效。

对于性能敏感服务,应该把 JIT 当成一个需要基准测试的构建变体。至少比较以下几组数据:

  • 冷启动时间和首个请求延迟;
  • 稳态吞吐、P50、P95 和 P99 延迟;
  • 进程 RSS、代码缓存和容器镜像大小;
  • 不同输入规模下的性能曲线;
  • 异常堆栈、调试器和 profiling 工具是否仍然可用。

只有在固定硬件、固定依赖和固定负载下得到稳定结果,才适合把 JIT 构建放进正式发布流程。一次微基准测试中的加速,不能直接推导出整个 Web 服务会按同样比例提速。

free-threaded 构建有了新的 Stable ABI 方向

Python 的 free-threaded 构建持续受到关注,Python 3.15 又为 C 扩展补充了面向 free-threaded 构建的 Stable ABI,名称为 abi3t。官方文档说明,目标 Stable ABI 的 C 扩展可以编译为与 free-threaded CPython 兼容的形式,但通常需要对源代码做不小的调整。

这解决的是扩展生态的长期兼容问题,不等于现有的二进制包会自动兼容。项目里只要使用了 NumPy、数据库驱动、加密库、图像处理库或自研 C 扩展,就需要查看依赖是否发布了对应 wheel,以及构建后是否真的通过了 free-threaded 测试。

迁移时可以把运行时矩阵明确列出来:

1
2
3
4
5
CPython 3.15 GIL 构建
CPython 3.15 free-threaded 构建
Linux / macOS / Windows
源码安装 / 二进制 wheel
常规测试 / 并发压力测试

free-threaded 主要改变的是并发执行的约束,不能替代对共享状态、线程安全和第三方库行为的检查。一个扩展即使能够成功安装,也可能在并发压力下暴露引用计数、全局状态或锁粒度方面的问题。

3.15 还有哪些值得留意的语言和标准库变化

除了性能和工具链,Python 3.15 还加入了一些会直接影响日常代码的特性:

  • PEP 814 增加内置不可变映射类型 frozendict,在键和值都可哈希时可以参与哈希操作;
  • PEP 661 增加内置 sentinel 类型,用于创建具有稳定身份和简洁表示的哨兵值;
  • PEP 798 允许列表、集合、字典推导式和生成器表达式使用 *** 解包;
  • PEP 686 让 UTF-8 成为默认编码;
  • PEP 728 为 TypedDict 增加 closedextra_items 等更严格的类型描述能力。

这些变化的迁移成本并不相同。frozendictsentinel 更像是新增工具,通常不会影响旧代码;UTF-8 默认编码则可能暴露以前依赖系统本地编码的文件处理问题;类型系统变化主要影响类型检查器、库作者和生成代码的工具链。

特别是编码问题,不能只在开发机上验证。应在 Linux、macOS、Windows 和不同区域设置下测试配置文件、CSV、日志、模板和命令行输出,检查隐式 open()sys.stdinsys.stdout 等路径是否仍符合预期。

项目现在应该怎样准备

先把 RC2 放进兼容性矩阵

不要直接替换系统 Python。为 3.15 RC2 创建独立虚拟环境,运行单元测试、集成测试、类型检查、打包和安装测试。对于发布库,还要在干净环境中验证源码包和 wheel,而不是只在维护者本机执行 pip install -e .

让 CI 暴露真正的失败位置

可以把测试拆成几类:导入测试、API 行为测试、编码测试、C 扩展构建测试、并发测试和性能基准。这样即使某一项只在 3.15 失败,也能知道是语言行为变化、构建链问题还是第三方依赖没有跟上。

把惰性导入和 JIT 分开评估

它们解决的是不同问题。惰性导入主要影响启动阶段和首次使用某个模块的成本,JIT 主要影响经过足够预热后的执行路径。两者一起打开后,性能变化更难归因,建议先单独测试,再做组合基准。

预先检查原生依赖

带 C、C++ 或 Rust 扩展的项目,至少应确认:

  • 构建后是否保留 frame-pointer 相关选项;
  • 依赖是否提供 Python 3.15 wheel;
  • free-threaded 构建是否有单独的 wheel 和测试结果;
  • 调试器、采样 profiler 与符号信息是否可用;
  • 生产基础镜像是否固定了 Python 小版本。

如果这些问题还没有答案,升级的主要风险往往不在 Python 代码本身,而在打包和运行环境。

结语

Python 3.15.0 RC2 是一个适合“提前验证”的版本。它把惰性导入、统一 profiling 包、JIT 覆盖范围、frame pointer 默认构建和 free-threaded Stable ABI 一起推进,影响了从应用启动到性能排查、再到原生扩展发布的多个环节。

对应用团队来说,当前最有价值的动作是把它加入 CI 和预发布环境,记录启动、延迟、内存、构建和依赖兼容数据;对库作者来说,则应尽快构建 3.15 wheel,检查 abi3t、编码默认值和新的导入语义。等正式版发布时,真正需要解决的问题应该已经从“能不能运行”变成“是否值得切换”。

参考资料

文章目录
  1. 1. 先看清 RC2 处在什么阶段
  2. 2. 显式惰性导入,先解决启动阶段的浪费
  3. 3. 统一 profiling 入口,让性能排查更容易组合
  4. 4. JIT 继续扩大覆盖范围,但不要把它当成开关
  5. 5. free-threaded 构建有了新的 Stable ABI 方向
  6. 6. 3.15 还有哪些值得留意的语言和标准库变化
  7. 7. 项目现在应该怎样准备
    1. 7.1. 先把 RC2 放进兼容性矩阵
    2. 7.2. 让 CI 暴露真正的失败位置
    3. 7.3. 把惰性导入和 JIT 分开评估
    4. 7.4. 预先检查原生依赖
  8. 8. 结语
  9. 9. 参考资料
|