PEP 576 – 规范内置函数类
- 作者:
- Mark Shannon <mark at hotpy.org>
- BDFL 委托:
- Petr Viktorin
- 状态:
- 已撤回
- 类型:
- 标准跟踪
- 创建日期:
- 2018年5月10日
- Python 版本:
- 3.8
- 发布历史:
- 2018年5月17日, 2018年6月23日, 2018年7月8日, 2019年3月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__(如果必要)。
高效调用第三方可调用对象
目前,大多数调用都通过“FastcallKeywords”内部调用约定分派给自定义代码中的function和method_descriptor。本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