Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 增强提案

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)。IronPythonJythonPyPy 都是 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

最后修改:2023 年 9 月 9 日 17:39:29 GMT