PEP 576 – 合理化内置函数类
- 作者:
- Mark Shannon <mark at hotpy.org>
- BDFL-代表:
- Petr Viktorin
- 状态:
- 已撤回
- 类型:
- 标准轨迹
- 创建:
- 2018-05-10
- Python 版本:
- 3.8
- 历史记录:
- 2018-05-17, 2018-06-23, 2018-07-08, 2019-03-29
摘要
将 CPython 内部使用的“FastcallKeywords”约定公开给第三方代码,并使 inspect
模块使用鸭子类型。结合起来,这将允许第三方 C 扩展和像 Cython 这样的工具创建使用与内置函数和 Python 函数相同的调用约定的对象,从而获得与 len
或 print
等内置函数相同的性能。
预计现有代码的性能会有所提升。
动机
目前,第三方模块作者在用 C 实现函数时面临两难选择。他们可以使用现有的内置函数或方法类,也可以用 C 实现自己的自定义类。第一个选择会导致他们失去访问可调用对象内部的能力。第二个选择是额外的维护负担,更重要的是,对性能有重大负面影响。
本 PEP 的目标是允许第三方 C 模块的作者以及像 Cython 这样的工具利用 CPython 内部用于内置函数和方法的更快调用约定,并且这样做不会损失相对于在 Python 中实现的函数的功能。
内省
inspect 模块在内省可调用对象时将完全支持鸭子类型。
函数 inspect.Signature.from_callable()
计算可调用对象的签名。如果对象具有 __signature__
属性,那么 inspect.Signature.from_callable()
只会返回该属性。为了进一步支持鸭子类型,如果可调用对象具有 __text_signature__
属性,那么将根据该属性创建 __signature__
。
这意味着第三方内置函数可以实现 __text_signature__
(如果足够的话),如果需要的话可以实现更昂贵的 __signature__
。
对第三方可调用对象的有效调用
目前,大多数调用通过自定义代码分派给 function
和 method_descriptor
,使用内部调用约定“FastcallKeywords”。本 PEP 提出通过 C 函数指针实现此调用约定。实现此二进制接口的第三方可调用对象有可能像内置函数一样快地被调用。
继续禁止可调用类作为基类
目前,任何尝试使用 function
、method
或 method_descriptor
作为新类的基类都将失败,并抛出 TypeError
。这种行为是可取的,因为它可以防止当子类覆盖 __call__
方法时发生错误。如果可调用对象可以被子类化,那么对 function
或 method_descriptor
的任何调用都需要额外的检查,以确保 __call__
方法没有被覆盖。通过公开额外的调用机制,错误的可能性会更大。因此,任何实现附加调用接口的第三方类都不能用作基类。
新类和现有类的更改
Python 可见更改
- 将添加一个新的内置类
builtin_function
。 types.BuiltinFunctionType
将引用builtin_function
而不是builtin_function_or_method
。- 类
builtin_function
的实例将保留builtin_function_or_method
的__module__
属性,并获得func_module
和func_globals
属性。func_module
允许访问函数所属的模块。请注意,这与__module__
属性不同,后者仅返回模块的名称。属性func_globals
等同于func_module.__dict__
,提供此属性是为了模拟 Python 函数同名属性的行为。 - 当将
method_descriptor
实例绑定到其所属类的实例时,将创建bound_method
而不是builtin_function_or_method
。这意味着method_descriptors
现在更像 Python 函数。换句话说,[].append
将变成bound_method
而不是builtin_function_or_method
。
C API 更改
- 添加一个新的函数
PyBuiltinFunction_New(PyMethodDef *ml, PyObject *module)
来创建内置函数。 PyCFunction_NewEx()
和PyCFunction_New()
已被弃用,如果可能,将返回PyBuiltinFunction
,否则返回builtin_function_or_method
。
在 C API 和 ABI 中保留向后兼容性
提议的更改在 API 和 ABI 级别上完全向后和向前兼容。
内部 C 更改
字段 typeobject.tp_flags
将允许使用两个新的标志。它们分别是 Py_TPFLAGS_EXTENDED_CALL
和 Py_TPFLAGS_FUNCTION_DESCRIPTOR
Py_TPFLAGS_EXTENDED_CALL
对于设置了 Py_TPFLAGS_EXTENDED_CALL
的任何内置类,与该内置类相对应的 C 结构体必须以结构体 PyExtendedCallable
开头,该结构体的定义如下:
typedef PyObject *(*extended_call_ptr)(PyObject *callable, PyObject** args,
int positional_argcount, PyTupleObject* kwnames);
typedef struct {
PyObject_HEAD
extended_call_ptr ext_call;
} PyExtendedCallable;
任何设置了 Py_TPFLAGS_EXTENDED_CALL
的类都不能用作基类,如果任何 Python 代码尝试将其用作基类,则会引发 TypeError。
Py_TPFLAGS_FUNCTION_DESCRIPTOR
如果为内置类 F
设置了此标志,那么该类的实例在用作类属性时,其行为应与 Python 函数相同。具体来说,这意味着 c.m
的值(其中 C.m
是内置类 F
的实例,而 c
是 C
的实例)必须是一个绑定方法,它将 C.m
和 c
绑定在一起。如果没有此标志,自定义可调用对象将无法像 Python 函数一样工作,并且像 Python 或内置函数一样高效。
对现有 C 结构体的更改
类 function
、method_descriptor
和 method
将更改其对应的结构体,以使它们以结构体 PyExtendedCallable
开头。
使用新的扩展调用接口的第三方内置类
为了使调用性能达到与 Python 函数和内置函数相同的水平,第三方可调用对象应设置 tp_flags
的 Py_TPFLAGS_EXTENDED_CALL
位,并确保相应的 C 结构体以 PyExtendedCallable
开头。任何设置了 Py_TPFLAGS_EXTENDED_CALL
位的内置类还必须实现 tp_call
函数,并确保其行为与 ext_call
函数一致。
这些更改的性能影响
在每个可调用对象中添加一个函数指针,而不是在每个可调用对象的类中添加一个函数指针,这使得可以在创建可调用对象时选择分派函数(用于整理参数并进行错误检查的代码),而不是在调用可调用对象时选择。这应该减少解释器中的调用站点与被调用者执行之间执行的指令数量。
替代建议
PEP 580 是解决与本 PEP 相同问题的另一种方法。
参考实现
可以在 https://github.com/markshannon/cpython/tree/pep-576-minimal 找到一个草案实现。
版权
本文档已置于公有领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0576.rst
上次修改: 2023-09-09 17:39:29 GMT