PyPy 8.0 值得关注,原因并不只是版本号发生了变化。这个项目发布的并非又一个单纯追求速度的 Python 解释器,而是在尝试降低选择 PyPy 时长期存在的一项成本:Python 语言运行时可以兼容,并不代表它能顺畅接入那个规模大得多、又高度依赖 CPython C API 的软件包世界。

一幅编辑插图:以 PyPy 为灵感的运行时通过兼容性桥梁连接到标准化的 Python 扩展包。

这次发布于 2026 年 9 月 19 日,加入了测试质量的 Python 3.12 解释器,同时继续提供 Python 3.11 和 Python 2.7 版本,并把 Linux 构建基线调整为 glibc 2.28。更重要的是,PyPy 团队表示,新的 Python 3.12 对象模型已经包含使用面向 CPython 3.12 构建的 cp312-abi3 wheel 所需的部分基础。剩余工作并不只在 PyPy 本身:导入机制、安装器和软件包构建系统,都必须认可这些 wheel 是有效候选。

因此,PyPy 8.0 适合实验和有边界的生产测试,但它并不是要求所有人替换 CPython。更实用的问题是:你的应用依赖能否在 PyPy 8.0 上运行,而不需要退回源码构建、不受支持的原生扩展,或只在 CPython 下成立的运行时假设?

PyPy 8.0 改变了什么

PyPy 将 8.0.0 描述为一个围绕三条解释器产品线构建的重大版本:PyPy2.7、PyPy3.11 和 PyPy3.12。Python 3.12 的实现明确标注为 beta,因此用户应把它视为兼容性测试平台,而不是成熟 CPython 部署的自动替代品。Python 3.11 版本仍然可用;项目表示,除非出现安全问题,这预计将是最后一个支持 Python 3.11 的版本。

PyPy 官方发布说明给出了版本号变化背后的两个基础设施原因。第一,Linux 构建机器人现在使用基于 AlmaLinux 8 和 glibc 2.28 的 manylinux 2.28 镜像,GCC 14 取代了旧的 GCC 5 工具链。因此生成的编译版 tarball 要求 glibc 2.28 或更高版本。对许多当前发行版而言,这是合理的基线,但仍然是一项部署约束:较旧的企业镜像、遗留设备以及严格冻结的容器基础镜像,都需要实际检查,不能想当然地认为兼容。

第二,PyPy 改变了内部对象表示向 C 扩展呈现的方式。早期版本以一种不同于 CPython 布局的形式,在 PyObject 结构中暴露 PyPy 专用扩展。8.0 中,这个 PyPy 专用字段被隐藏在传给 C 扩展模块的指针之前的前缀中。项目的目标,是让 PyPy 能够使用为 CPython 3.12 及以后版本生成的 cp312-abi3 wheel,前提是这些 wheel 确实没有越出 limited API 的范围。

该版本还更新了 RPython 代码生成。PyPy 现在在生成的解释器代码中使用 computed goto,并进行更激进的内联。团队坦率地说,性能提升没有达到预期。这个细节很重要:这不是一个价值可以被压缩成单个基准数字的版本。它更有影响力的工作在于软件包、兼容性,以及支持另一种解释器所带来的维护成本。

PyPy 也移除了内部 HPy 后端。发布说明称,HPy 基于句柄的方法是一个有用的原型,但没有获得足够支持,因而未能成为新的标准。相关 HPy 代码仍保留在 PyPy 代码树中,也可以通过构建选项启用,但它不再代表默认方向。对扩展作者而言,眼下的兼容路径仍是现有 C API 边界、CFFI,或针对解释器的构建逻辑,而不是一个能够消除旧有取舍的广泛 HPy 迁移。

为什么 cp312-abi3 重要

Python 软件包并不是都以同一种方式发布。纯 Python 软件包通常可以使用 py3-none-any wheel,在 CPython、PyPy 或其他 Python 3 实现上运行。包含编译后 C、C++ 或 Rust 代码的软件包则不同。它的 wheel 可能绑定到特定解释器、ABI、操作系统和处理器架构。文件名会编码这些兼容性声明。

Python Packaging User Guide 将 abi3 描述为面向 CPython stable ABI 构建的扩展所使用的标签。stable ABI 是 C API 中受限的子集,设计目标是在不同 Python 3 版本之间持续可用。常见情况下,软件包可以针对每个操作系统和架构组合发布一个 cp39-abi3 wheel,而不用为每个 CPython 小版本分别重新构建扩展。

这项好处不会自动延伸到 PyPy。标为 cp312-abi3 的 wheel 是在声明自己符合 CPython stable ABI。PyPy 必须提供相关接口的兼容实现,软件包工具也必须在依赖解析时考虑这些 wheel。PyPy 8.0 的发布说明称,C 头文件和导出函数现在已经与 Python 3.12 的 CPython limited API 对齐,但也点出了两个重要缺口:导入机制必须接受 abi3.so 共享对象作为 PyPy 的有效文件,pipuv 等工具也必须将这些 wheel 识别为候选。

这正是该版本的核心。运行时层面的变化在技术上可以完全正确,但如果软件包索引、安装器、构建后端和扩展项目没有采用相同解释,用户最终仍然几乎得不到实际收益。兼容链大致如下:

PyPy C 头文件与加载器
        ↓
扩展项目在 limited API 范围内构建
        ↓
wheel 元数据声明兼容 ABI
        ↓
安装器将该 wheel 视为 PyPy 候选
        ↓
应用通过运行时与行为测试

其中任何一环断裂,用户就会被送回源码构建、PyPy 专用 wheel、纯 Python 回退实现,或不兼容的二进制文件。PyPy 8.0 推进了这条链的第一部分,但并没有消除测试其余环节的必要。

兼容性限制仍然很大

最需要避免的误读,是把“支持 CPython 3.12 abi3 wheel”理解成“支持所有流行 Python 软件包”。许多软件包并不使用 limited API。它们可能会深入 CPython 的实现细节,依赖引用计数行为,假定特定对象布局,或者发布只在 CPython 上经过测试的生成代码。某个 wheel 的标签看起来接近可移植,并不代表其中代码没有依赖 PyPy 无法完全复现的假设。

PyPy 长期提供名为 cpyext 的兼容层,用来模拟大量 CPython C API。这让许多扩展代码能够运行,但模拟本身有代价。PyPy 使用 tracing garbage collector,而 CPython 使用引用计数实现。通过兼容层观察到的引用计数,不一定表示 C 扩展在 CPython 下会看到的同一种状态。将引用计数作为生命周期信号、执行精细的释放工作,或依赖 CPython 专有对象内部结构的代码,都应当特别谨慎。

Cython 文档也指出了相近的问题:Cython 可以调整生成代码以适配 PyPy,但模拟 C API 仍然存在可见差异。文档建议,扩展作者在可能的情况下依赖 Cython 生成的处理逻辑,而不是在没有明确理由时直接使用底层 C API 行为。这不是对 PyPy 的专门否定,而是在提醒:稳定的语言表面和稳定的原生扩展表面,是两个不同的工程问题。

对于需要支持多个 Python 实现的项目,CFFI 仍然是重要替代方案。PyPy 团队特别建议使用 C 扩展的库维护者考虑提供一个在 PyPy 上表现良好的 CFFI 版本。CFFI 不会神奇地让每个原生依赖都具备可移植性,但它可以避开部分让 CPython 扩展难以在其他解释器下运行的假设。

Rust 扩展作者面对的决策也类似。PyO3 支持构建 PyPy,并提供检测 PyPy 实现的配置,但它的文档和源代码注释仍把 stable ABI 视为以 CPython 为中心的路径。因此,希望同时支持 CPython 和 PyPy 的 Rust 项目,应明确构建和测试这两个目标。不能仅因为 CPython 构建使用了 abi3,就推断它自动兼容 PyPy。

现在谁适合尝试 PyPy 8.0

最合适的候选应用,是包含长时间运行的 Python 循环、相当多纯 Python 计算,或能够在代码进入热态后从 PyPy tracing JIT 获益的服务。如果应用大部分时间花在 Python 层代码上,而不是等待已经完成主要工作的原生库,那么潜在收益更可信。

对纯 Python 库维护者而言,PyPy 也是合理的测试平台。如果一个软件包声称广泛兼容不同 Python 实现,把 PyPy 8.0 加入 CI 矩阵,可能暴露出仅靠 CPython 测试无法发现的假设。对于间接操作对象生命周期、大量使用动态分派,或承诺支持替代解释器的库,这一点尤其有用。

扩展维护者同样与这个版本有关。已经使用 Cython、CFFI 或 PyO3 的项目,可以借助 PyPy 8.0 判断自己的抽象边界是否真正具备可移植性。新的 cp312-abi3 工作,为 PyPy、扩展作者和软件包工具之间的协作提供了一个具体目标。一个宽泛的请求——“让 PyPy 获得更好支持”——被拆成了可测试的问题:扩展能否编译,wheel 能否安装,能否导入,以及在垃圾回收和 JIT 执行下行为是否正确?

维护 Python 服务的团队应当更有选择地进行评估。依赖树适中、集成测试扎实、工作负载主要由 Python 代码构成的服务,可以作为试点。包含多个大型二进制依赖、原生数据库驱动、自定义 Cython 模块和供应商专用 wheel 的数据科学栈,则是风险高得多的首个目标。它最终可能可以运行,但预期收益必须足以证明维护另一条解释器路径的成本。

尝试 PyPy 8.0 最没有说服力的理由,是版本号比 CPython 的版本号大。PyPy 的版本号属于项目自己的发布序列,并不意味着运行时实现了 Python 8。本版本涉及的语言版本是 Python 3.12 beta、Python 3.11 和 Python 2.7。

一套实际的评估方案

合理的测试应该从清点开始,而不是从基准测试开始。记录完整 lockfile,并将每个依赖分类为纯 Python、C 扩展、Rust 扩展、外部共享库,或具有已知解释器专用路径的软件包。结果会告诉你真正的工作在哪里。只列出顶层需求不够,因为最脆弱的软件包可能位于依赖图的好几层之下。

接着,为 PyPy 8.0 创建独立环境,并使用项目平时采用的解析器安装。不要先装入一组 CPython wheel,再假定这个环境具有代表性。记录哪些软件包从 wheel 安装,哪些从源码构建,哪些被拒绝。安装成功是有用证据,但不是兼容性结论。

随后,测试套件至少应运行在四种模式下:干净启动、短时功能运行、长时间工作负载,以及重启或升级路径。长时间模式很重要,因为 PyPy 的 JIT 需要时间预热,垃圾回收行为也可能不会出现在小规模单元测试中。测量启动时间、稳定状态吞吐量、内存增长、尾延迟,以及服务达到可用状态所需的时间。更快的内层循环不一定意味着更好的部署;如果启动或内存成本主导了工作负载,结论可能相反。

原生边界需要集中测试。覆盖序列化、数据库驱动、图像或音频处理、密码学、压缩、数值内核,以及任何通过 C 或 Rust 传递 Python 对象的代码。测试应当强制发生分配和回收,而不只是调用成功路径。如果应用使用回调、终结器、缓冲区协议或线程协调,也要明确包含这些路径。这些地方最容易让解释器差异从安装警告变成运行故障。

保留 CPython 作为比较基线。目标不是证明 PyPy 在每个基准上都获胜,而是比较完整的工程结果:构建可靠性、冷启动行为、内存、吞吐量、可观测性、调试、wheel 可用性,以及同时维护两条解释器路径所需的时间。运行数小时的批处理任务,预热成本可能微不足道;每分钟启动数百次的命令行工具,则可能被同一成本抵消收益。

Linux 用户应检查 glibc 边界

转向 glibc 2.28 很容易被忽略,因为它常被描述成构建基础设施变化。但它同时也是运行时分发契约的一部分。PyPy 下载页面说明,当前 Linux 二进制文件兼容 manylinux 2.28 及以后版本,并指出 Linux 构建要求 glibc 2.28 或更高版本。使用现代 Ubuntu、Debian、Fedora 及类似系统的用户,大概率不会觉得意外;支持旧发行版或最小化镜像的用户,则应直接验证。

容器构建是发现问题最简单的地方。测试生产环境实际使用的基础镜像,而不是更新的开发者镜像。检查解释器的共享库要求,并在该镜像中运行应用的冒烟测试。如果部署目标包含多种机器,测试最旧的受支持节点。能在构建主机运行、却不能在最旧生产主机运行的 PyPy 二进制文件,并不算成功升级。

项目还提醒,Linux 二进制文件包含 OpenSSL,但不包含证书存储。下载文档建议依赖平台证书存储,或配置 SSL_CERT_FILE,例如通过 certifi 软件包提供。这个问题并非 PyPy 独有,但在下载自包含解释器归档时,正是容易遗漏的运行细节。对于会访问软件包索引、API 或内部服务的工具,HTTPS 测试应当成为初始验证的一部分。

下载、验证并隔离实验

PyPy 为多个平台提供预编译归档,包括 Linux x86-64、Linux ARM64、64 位 Windows,以及 Apple Silicon 和 Intel 硬件上的 macOS。项目为 8.0.0 归档发布了校验和。应把校验和视为安装过程的一部分:从项目官方页面下载,验证归档,并在实验记录中记下确切的解释器构建版本。

评估时不要替换系统 python 命令。使用专用虚拟环境或明确的解释器路径,保留现有 CPython 环境,并让 CI 中的选择清晰可见。小型包装脚本或矩阵条目更容易移除;全局修改解释器则可能悄悄改变脚本、构建任务或服务单元。

项目表示,从源码构建 PyPy 耗时较长,并且需要相当多的计算资源,因此大多数用户应从官方二进制文件开始。源码构建适合发行版打包者、PyPy 贡献者,或对工具链有受控要求的团队;它不是普通应用评估的最短路径。

这个版本对软件包工具意味着什么

PyPy 8.0 暴露了一个 Python 生态多年推迟处理的协调问题。解释器支持经常被描述为运行时本身的属性,但 wheel 选择实际上是软件包元数据、安装器标签、构建后端、索引策略,以及最终加载二进制文件的实现共同协商的结果。PyPy 团队自己的措辞已经明确说明,导入机制以及 pipuv 等工具仍有工作要做。

这也给软件包维护者提供了具体的参与方式。维护者可以在 PyPy 3.12 上测试扩展,检查它是否真的只使用 limited API,增加 PyPy CI 任务,在合适时发布兼容 wheel,并用最小复现报告失败。用户如果只提交“PyPy 坏了”,提供的信号很少;如果报告“cp312-abi3 wheel 可以安装,但在回收后释放缓冲区时失败”,生态就有了可以修复的具体问题。

这同样提醒维护者准确填写软件包元数据。纯 Python 分发包应使用最宽但真实的标签。编译扩展应只声明实际测试过的 ABI 和平台。一个项目即使用 stable ABI 构建,却仍然导入 CPython 私有符号,也没有实现文件名所暗示的可移植性。PyPy 8.0 的工作只有在软件包声明变得更准确,而不是仅仅更乐观时,才会产生真正价值。

替代方案与互补路径

CPython 仍然是软件包兼容范围最广、原生扩展行为最可预测、供应商支持 wheel 集合最大的默认选择。对许多应用而言,这就是正确决定。选择 PyPy 不是关于 Python 实现多样性的道德表态,而是工作负载和维护成本的决定。

如果目标是在保留熟悉运行时的同时减少依赖摩擦,改善软件包的 C 边界可能比更换解释器更有用。CFFI、定义清楚的 limited API、生成式绑定,以及更少的实现专用假设,都能改善 CPython、PyPy 和未来运行时之间的可移植性。对于 Rust 扩展,明确构建 PyPy 目标并使用条件编译,往往比期待一个面向 CPython 的产物处处可用更可靠。

如果目标是原始速度,应使用真实应用对可选方案进行基准测试。PyPy 的 JIT 可以帮助长期运行且以 Python 为主的工作负载,但原生库、I/O、序列化、启动和内存也可能成为主导因素。经过仔细优化的 CPython 部署、不同算法、编译扩展或进程架构变化,可能带来更大收益。正确的比较对象是完整程序,而不是合成循环。

结论

PyPy 8.0 是一个有分量的开源版本,因为它正面处理了一个真实的采用障碍。Python 3.12 beta 支持让项目更接近当前语言生态。转向 glibc 2.28 为 Linux 二进制文件提供了更现代的发行版基线。新的对象模型和计划中的 cp312-abi3 支持,可能减少需要单独 PyPy 构建的软件包数量。

限制同样重要:只有当安装器选择这些 wheel、加载器接受它们,并且应用能够承受真实工作负载时,兼容性才算完整。CPython 专用 C 扩展、引用计数假设、二进制依赖和测试覆盖不均衡等旧问题仍然存在。PyPy 8.0 改善了起点,但不会让依赖图凭空消失。

对开发者而言,实际建议是:用固定 lockfile 和 CPython 对照,为一个边界清晰的工作负载试用 PyPy 8.0。如果库声明支持替代解释器,就把它加入 CI。检查 glibc 基线,验证归档校验和,测试 HTTPS 证书,并逐项审视原生依赖。把 Python 3.12 支持视为 beta,把 cp312-abi3 兼容性视为仍在推进中的生态项目。

这已经足以让该版本值得尝试。PyPy 最有力的理由从来不是所有 Python 程序都应该切换,而是 Python 生态应当拥有不止一种严肃的实现,并且选择其中一种时,不必把普通软件包管理变成独立的工程项目。PyPy 8.0 推进了这个目标,但下一步同样属于库维护者和软件包工具,而不只是解释器团队。

来源

本文事实依据包括 PyPy 官方《PyPy v8.0.0 release》发布说明、《Download and Install》下载文档、《Checksums》校验和页面,以及 pypy/pypy 项目资料。软件包标签、二进制扩展和 limited API 的背景参考 Python Packaging User Guide 的《Platform compatibility tags》和《Packaging binary extensions》。关于 Cython 的 PyPy 适配,参考 Cython 文档《Porting Cython code to PyPy》;关于 Rust 扩展构建与分发,参考 PyO3 用户指南《Building and distribution》。生态讨论部分参考 Hacker News 上的 PyPy v8.0.0 发布讨论。