PEP 399 – 纯 Python/C 加速器模块兼容性要求
- 作者:
- Brett Cannon <brett at python.org>
- 状态:
- 最终
- 类型:
- 信息
- 创建:
- 2011 年 4 月 4 日
- Python 版本:
- 3.3
- 历史记录:
- 2011 年 4 月 4 日,2011 年 4 月 12 日,2011 年 7 月 17 日,2011 年 8 月 15 日,2013 年 1 月 1 日
摘要
CPython 下的 Python 标准库包含各种模块,这些模块既可以用纯 Python 实现,也可以用 C 实现(全部或部分)。本 PEP 要求在这些情况下,C 代码 **必须** 通过用于纯 Python 代码的测试套件,以尽可能地作为直接替换(C 和 VM 特定的测试除外)。对于没有纯 Python 等效实现的新的基于 C 的模块,也要求获得特殊许可才能添加到标准库中。
基本原理
Python 已经超越了 CPython 虚拟机 (VM)。IronPython、Jython 和 PyPy 都是 CPython VM 当前可行的替代方案。围绕 Python 编程语言发展起来的 VM 生态系统导致 Python 被用于许多 CPython 不能使用的领域,例如,Jython 允许 Python 在 Java 应用程序中使用。
除了 CPython 之外的所有 VM 都面临的一个问题是处理标准库中以 C 实现(在某种程度上)的模块。由于其他 VM 通常不支持 CPython 的整个 C API,因此它们无法使用用于创建模块的代码。这通常会导致这些其他 VM 只能用纯 Python 或用于实现 VM 本身的编程语言(例如,IronPython 中的 C#)重新实现这些模块。这种在 CPython、PyPy、Jython 和 IronPython 之间重复的工作非常不幸,因为 **至少** 用纯 Python 实现一个模块将有助于缓解这种重复工作。
本 PEP 的目的是通过规定添加到 Python 标准库中的所有新模块 **必须** 具有纯 Python 实现来最大程度地减少这种重复工作,除非获得特殊许可。这确保了 stdlib 中的模块对所有 VM 都可用,而不仅仅是 CPython(不满足此要求的现有模块除外,尽管没有任何东西可以阻止人们在事后添加纯 Python 实现)。
出于性能原因,仍然允许用 C(在 CPython 的情况下)重新实现模块的某些部分(或全部),但任何此类加速代码必须通过相同的测试套件(不包括 VM 或 C 特定的测试)以验证语义并防止出现差异。为了实现这一点,模块的测试套件在添加加速代码之前必须对纯 Python 实现进行全面覆盖。
细节
从 Python 3.3 开始,添加到标准库中的任何模块都必须具有纯 Python 实现。只有在 Python 开发团队为该模块授予特殊豁免的情况下,才能忽略此规则。通常,只有当模块包装特定基于 C 的库时(例如,sqlite3)才会授予豁免。在授予豁免时,将承认该模块将被视为 CPython 独有的,而不是其他 VM 预计支持的 Python 标准库的一部分。使用 ctypes
为 C 库提供 API 将继续受到反对,因为 ctypes
缺乏 C 代码通常依赖的编译器保证,以防止某些错误发生(例如,API 更改)。
即使本 PEP 规定了纯 Python 实现,它并不排除使用配套加速模块。如果提供了加速模块,它的命名应与它加速的模块相同,但在前面加上下划线作为前缀,例如,_warnings
用于 warnings
。从纯 Python 实现访问加速代码的常见模式是用 import *
导入它,例如,from _warnings import *
。这通常在模块末尾完成,以允许它用加速的等效项覆盖特定的 Python 对象。如果需要,这种类型的导入也可以在模块末尾之前完成,例如,提供了加速的基类,但随后由 Python 代码进行子类化。本 PEP 并不强制要求 stdlib 中缺乏纯 Python 等效项的现有模块获得此类模块。但如果人们自愿提供和维护纯 Python 等效项(例如,PyPy 团队自愿提供他们对 csv
模块的纯 Python 实现并维护它),那么此类代码将被接受。在这些情况下,C 版本在预期语义方面被认为是参考实现。
任何新的加速代码必须尽可能接近纯 Python 实现,作为直接替换。提供加速代码的 VM 的技术细节允许根据需要进行差异化,例如,一个类在用 C 实现时是 type
。为了验证 Python 和等效 C 代码尽可能类似地运行,这两个代码库必须使用相同的测试进行测试,这些测试适用于纯 Python 代码(特定于 C 代码或任何 VM 的测试不属于此要求)。测试套件应很全面,以验证预期语义。
作为直接替换还规定,加速代码中不应提供任何在纯 Python 代码中不存在的公共 API。如果没有此要求,人们可能会意外地依赖于加速代码中的某个细节,而该细节没有提供给使用纯 Python 实现的其他 VM。为了帮助验证语义等效性的契约是否得到满足,必须尽可能彻底地测试模块,无论它是否包含其加速代码。
例如,要编写测试以同时执行模块的纯 Python 和 C 加速版本,可以遵循基本习惯用法
from test.support import import_fresh_module
import unittest
c_heapq = import_fresh_module('heapq', fresh=['_heapq'])
py_heapq = import_fresh_module('heapq', blocked=['_heapq'])
class ExampleTest:
def test_example(self):
self.assertTrue(hasattr(self.module, 'heapify'))
class PyExampleTest(ExampleTest, unittest.TestCase):
module = py_heapq
@unittest.skipUnless(c_heapq, 'requires the C _heapq module')
class CExampleTest(ExampleTest, unittest.TestCase):
module = c_heapq
if __name__ == '__main__':
unittest.main()
测试模块定义一个基类(ExampleTest
),其中包含通过 self.heapq
类属性访问 heapq
模块的测试方法,以及将此属性设置为模块的 Python 或 C 版本的两个子类。请注意,只有两个子类继承自 unittest.TestCase
- 这可以防止 ExampleTest
类被 unittest
测试发现检测为 TestCase
子类。一个 skipUnless
装饰器可以添加到测试 C 代码的类中,以便在 C 模块不可用时跳过这些测试。
如果此测试要对纯 Python 实现中的 heapq.heappop()
进行全面覆盖,那么允许将加速的 C 代码添加到 CPython 的标准库中。如果它没有,那么需要更新测试套件,直到提供适当的覆盖范围,然后才能添加加速的 C 代码。
为了进一步帮助实现兼容性,C 代码应使用对象的抽象 API,以防止意外地依赖于特定类型。例如,如果函数接受序列,那么 C 代码应默认使用 PyObject_GetItem()
,而不是类似于 PyList_GetItem()
的东西。如果使用正确的 PyList_CheckExact()
,C 代码允许使用快速路径,但在其他情况下,API 应与任何对正确接口进行鸭子类型的对象一起使用,而不是特定类型。
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0399.rst