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之外,所有虚拟机都面临一个问题,即如何处理标准库中(在某种程度上)用C语言实现的模块。由于其他虚拟机通常不支持CPython的完整C API,它们无法使用用于创建模块的代码。这常常导致这些其他虚拟机要么用纯Python重新实现模块,要么用实现虚拟机本身的编程语言(例如,IronPython使用C#)重新实现。CPython、PyPy、Jython和IronPython之间的这种重复工作是极其不幸的,因为至少用纯Python实现模块将有助于减轻这种重复工作。
本PEP的目的是通过规定所有添加到Python标准库的新模块必须具有纯Python实现,除非获得特殊豁免,从而最大限度地减少这种重复工作。这确保了标准库中的模块对所有虚拟机都可用,而不仅仅是CPython(不符合此要求的现有模块是豁免的,尽管没有人阻止人们追溯性地添加纯Python实现)。
出于性能原因,允许用C语言重新实现模块的部分(或全部)(在CPython的情况下),但任何此类加速代码都必须通过相同的测试套件(不包括VM或C特定的测试)以验证语义并防止差异。为此,在添加加速代码之前,模块的测试套件必须对纯Python实现进行全面的覆盖。
详情
从Python 3.3开始,任何添加到标准库的模块都必须有纯Python实现。只有在Python开发团队对该模块给予特殊豁免时,才能忽略此规则。通常,豁免只在模块封装了特定的C语言库时(例如,sqlite3)才会授予。在授予豁免时,该模块将被视为CPython独有,不属于其他虚拟机应支持的Python标准库的一部分。使用ctypes
来为C库提供API将继续受到反对,因为ctypes
缺乏C代码通常依赖的编译器保证,以防止某些错误发生(例如,API更改)。
尽管本PEP规定必须有纯Python实现,但这并不排除使用配套的加速模块。如果提供了加速模块,其名称应与被加速的模块相同,并带有一个下划线作为前缀,例如,warnings
的加速模块名为_warnings
。从纯Python实现访问加速代码的常见模式是使用import *
导入,例如,from _warnings import *
。这通常在模块的末尾完成,以允许它用其加速的等效项覆盖特定的Python对象。这种导入也可以在模块末尾之前完成,例如,提供了加速基类,然后由Python代码进行子类化。本PEP不强制标准库中缺乏纯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
——这可以防止unittest
测试发现将ExampleTest
类检测为TestCase
子类。可以在测试C代码的类中添加skipUnless
装饰器,以便在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