PEP 575 – 统一函数/方法类
- 作者:
- Jeroen Demeyer <J.Demeyer at UGent.be>
- 状态:
- 已撤回
- 类型:
- 标准跟踪
- 创建:
- 2018-03-27
- Python 版本:
- 3.8
- 历史记录:
- 2018-03-31, 2018-04-12, 2018-04-27, 2018-05-05
撤回通知
参见 PEP 580,了解允许快速调用自定义类的更好解决方案。
参见 PEP 579,了解有关本 PEP 中其他问题的一些更广泛的讨论。
摘要
重新组织函数和方法的类层次结构,目标是减少内置函数(在 C 中实现)和 Python 函数之间的差异。主要的是,让内置函数的行为更像 Python 函数,而不会牺牲性能。
引入了新的基类 base_function
,各种函数类以及 method
(重命名为 bound_method
)都继承自它。
我们还允许子类化 Python function
类。
动机
目前,CPython 有两个不同的函数类:第一个是 Python 函数,当你使用 def
或 lambda
定义函数时得到的函数。第二个是内置函数,例如 len
、isinstance
或 numpy.dot
。这些是在 C 中实现的。
这两个类是完全独立实现的,并且具有不同的功能。特别是,目前无法在 C 中有效地实现函数(只有内置函数可以做到这一点),同时仍然允许像 inspect.signature
或 inspect.getsourcefile
这样的自省(只有 Python 函数可以做到这一点)。这对像 Cython [1] 这样的项目来说是一个问题,这些项目希望做到这一点。
在 Cython 中,这是通过发明一个名为 cyfunction
的新函数类来解决的。不幸的是,新的函数类会产生问题:inspect
模块不识别这些函数为函数 [2],并且性能更差(CPython 对调用内置函数有特定的优化)。
第二个动机是更普遍地使内置函数和方法的行为更像 Python 函数和方法。例如,Python 未绑定方法只是函数,但扩展类型的未绑定方法(例如 dict.get
)是一个不同的类。Python 类的绑定方法具有 __func__
属性,扩展类型的绑定方法没有。
第三,本 PEP 允许对函数进行高度定制。function
类变得可以子类化,并且还允许使用自定义函数子类来表示在 C 中实现的函数。在后一种情况下,这可以通过与真正的内置函数相同的性能来完成。所有函数都可以访问函数对象(__call__
中的 self
),为 PEP 573 铺平了道路。
新类
这是函数和方法的新类层次结构
object
|
|
base_function
/ | \
/ | \
/ | defined_function
/ | \
cfunction (*) | \
| function
|
bound_method (*)
用 (*) 标记的两个类 *不允许* 子类化;其他类则允许。
函数和未绑定方法之间没有区别,而绑定方法是 bound_method
的实例。
base_function
类 base_function
成为所有函数类型的新的基类。它基于现有的 builtin_function_or_method
类,但具有以下差异和新功能
- 它充当一个描述符,实现
__get__
,如果m_self
为NULL
,则将函数转换为方法。如果m_self
不为NULL
,那么这是一个无操作:返回现有函数。 - 一个新的只读属性
__parent__
,在 C 结构中表示为m_parent
。如果此属性存在,它表示定义对象。对于扩展类型的 method,这是定义类(在普通 Python 中为__class__
),对于模块的 function,这是定义模块。通常,它可以是任何 Python 对象。如果__parent__
是一个类,它具有特殊语义:在这种情况下,必须使用self
是该类的实例来调用该函数。最后,__qualname__
和__reduce__
将使用__parent__
作为命名空间(而不是之前的__self__
)。 - 一个新的属性
__objclass__
,如果__parent__
是一个类,则等于__parent__
。否则,访问__objclass__
会引发AttributeError
。这旨在与method_descriptor
向后兼容。 - 字段
ml_doc
以及属性__doc__
和__text_signature__
(参见 Argument Clinic)不受支持。 - 一个新的标志
METH_PASS_FUNCTION
,用于ml_flags
。如果设置了此标志,则存储在ml_meth
中的 C 函数将使用一个额外的第一个参数调用,该参数等于函数对象。 - 一个新的标志
METH_BINDING
,用于ml_flags
,它只适用于模块的函数(而不是类的 method)。如果设置了此标志,则m_self
将设置为NULL
而不是模块。这允许函数的行为更像 Python 函数,因为它启用了__get__
。 - 一个新的标志
METH_CALL_UNBOUND
用于禁用 自我切片。 - 一个新的标志
METH_PYTHON
,用于ml_flags
。此标志表示此函数应被视为 Python 函数。理想情况下,应避免使用此标志,因为它违反了鸭子类型哲学。但是,它在某些地方仍然需要,例如 分析。
base_function
的目标是它支持所有不同的方式来调用函数和方法,只需一个结构。例如,新的标志 METH_PASS_FUNCTION
将由方法的实现使用。
无法直接创建 base_function
的实例(tp_new
为 NULL
)。但是,C 代码手动创建实例是合法的。
这些是相关的 C 结构
PyTypeObject PyBaseFunction_Type;
typedef struct {
PyObject_HEAD
PyCFunctionDef *m_ml; /* Description of the C function to call */
PyObject *m_self; /* __self__: anything, can be NULL; readonly */
PyObject *m_module; /* __module__: anything (typically str) */
PyObject *m_parent; /* __parent__: anything, can be NULL; readonly */
PyObject *m_weakreflist; /* List of weak references */
} PyBaseFunctionObject;
typedef struct {
const char *ml_name; /* The name of the built-in function/method */
PyCFunction ml_meth; /* The C function that implements it */
int ml_flags; /* Combination of METH_xxx flags, which mostly
describe the args expected by the C func */
} PyCFunctionDef;
子类可以使用额外的字段扩展 PyCFunctionDef
。
Python 属性 __self__
返回 m_self
,除非设置了 METH_STATIC
。在这种情况下或如果 m_self
为 NULL
,那么根本没有 __self__
属性。因此,在本 PEP 中,我们编写 m_self
或 __self__
,它们的含义略有不同。
cfunction
这是旧版 builtin_function_or_method
类的全新版本。名称 cfunction
的选择是为了避免与“内置”一词的混淆,这里的“内置”是指“builtins
模块中的内容”。这个名称也更符合 C API,C API 使用 PyCFunction
前缀。
类 cfunction
是 base_function
的副本,但有一些差异:
m_ml
指向一个PyMethodDef
结构,它扩展了PyCFunctionDef
,并添加了一个ml_doc
字段来实现__doc__
和__text_signature__
作为只读属性。typedef struct { const char *ml_name; PyCFunction ml_meth; int ml_flags; const char *ml_doc; } PyMethodDef;
请注意,
PyMethodDef
是 Python 稳定 ABI 的一部分,它被几乎所有扩展模块使用,因此我们绝对不能更改此结构。- 参数诊所 受支持。
__self__
始终存在。在base_function.__self__
会引发AttributeError
的情况下,将返回None
。
类型对象是 PyTypeObject PyCFunction_Type
,我们定义 PyCFunctionObject
作为 PyBaseFunctionObject
的别名(除了 m_ml
的类型)。
defined_function
类 defined_function
是一个抽象基类,用于指示函数具有内省支持。defined_function
的实例需要支持 Python 函数的所有属性,即 __code__
、__globals__
、__doc__
、__defaults__
、__kwdefaults__
、__closure__
和 __annotations__
。还有一个 __dict__
用于支持用户添加的属性。
这些属性都不需要有意义。特别是,__code__
可能不是一个可用的代码对象,可能只填写了一些字段。本 PEP 并没有规定如何实现各种属性。它们可以是简单的结构体成员,也可以是更复杂的描述符。只需要只读支持,不需要任何属性可写。
类 defined_function
主要用于自动生成的 C 代码,例如由 Cython [1] 生成的代码。没有 API 可以创建它的实例。
C 结构如下所示:
PyTypeObject PyDefinedFunction_Type;
typedef struct {
PyBaseFunctionObject base;
PyObject *func_dict; /* __dict__: dict or NULL */
} PyDefinedFunctionObject;
TODO:也许可以为 defined_function
找到一个更好的名称。其他提议:inspect_function
(任何满足 inspect.isfunction
的内容)、builtout_function
(一个构建得更好的函数;玩弄了内置一词)、generic_function
(最初的提议,但与 functools.singledispatch
泛型函数冲突)、user_function
(由用户定义,而不是 CPython)。
function
此类专为用 Python 实现的函数而设计。与其他函数类型不同,function
的实例可以从 Python 代码创建。这没有变化,因此我们不会在本 PEP 中描述这些细节。
C 结构的布局如下所示:
PyTypeObject PyFunction_Type;
typedef struct {
PyBaseFunctionObject base;
PyObject *func_dict; /* __dict__: dict or NULL */
PyObject *func_code; /* __code__: code */
PyObject *func_globals; /* __globals__: dict; readonly */
PyObject *func_name; /* __name__: string */
PyObject *func_qualname; /* __qualname__: string */
PyObject *func_doc; /* __doc__: can be anything or NULL */
PyObject *func_defaults; /* __defaults__: tuple or NULL */
PyObject *func_kwdefaults; /* __kwdefaults__: dict or NULL */
PyObject *func_closure; /* __closure__: tuple of cell objects or NULL; readonly */
PyObject *func_annotations; /* __annotations__: dict or NULL */
PyCFunctionDef _ml; /* Storage for base.m_ml */
} PyFunctionObject;
描述符 __name__
返回 func_name
。当设置 __name__
时,还会用 UTF-8 编码的名称更新 base.m_ml->ml_name
。
字段 _ml
保留了要由 base.m_ml
使用的空间。
一个 base_function
实例必须设置标志 METH_PYTHON
当且仅当它是一个 function
实例。
当从 code
和 globals
构造一个 function
实例时,将创建一个实例,其中 base.m_ml = &_ml
、base.m_self = NULL
。
为了让子类化更容易,我们还添加了一个复制构造函数:如果 f
是 function
的实例,那么 types.FunctionType(f)
将复制 f
。这方便地允许使用自定义函数类型作为装饰器。
>>> from types import FunctionType
>>> class CustomFunction(FunctionType):
... pass
>>> @CustomFunction
... def f(x):
... return x
>>> type(f)
<class '__main__.CustomFunction'>
这也消除了许多 functools.wraps
的用例:包装器可以由 function
的子类替换。
bound_method
类 bound_method
用于所有绑定方法,无论底层函数的类是什么。它在 base_function
的基础上添加了一个新属性:__func__
指向该函数。
bound_method
替换了旧的 method
类,该类仅用于绑定为方法的 Python 函数。
这里有一个复杂之处,因为我们希望允许从任意可调用对象构造一个方法。这可能是一个已绑定方法,也可能根本不是 base_function
的实例。因此,在实践中,有两种方法:
- 对于任意可调用对象,我们使用一个带有
METH_PASS_FUNCTION
标志设置的固定PyCFunctionDef
结构。 - 对于绑定
base_function
实例的方法(更确切地说,具有Py_TPFLAGS_BASEFUNCTION
标志设置)并且具有 自身切片 的方法,我们使用原始函数的PyCFunctionDef
。这样,我们在调用绑定方法时就不会损失任何性能。在这种情况下,__func__
属性仅用于实现各种属性,而不是用于调用方法。
当从 base_function
构造一个新的方法时,我们检查 self
对象是否是 __objclass__
的实例(如果指定了类作为父类)并在出现错误时引发 TypeError
。
C 结构如下所示:
PyTypeObject PyMethod_Type;
typedef struct {
PyBaseFunctionObject base;
PyObject *im_func; /* __func__: function implementing the method; readonly */
} PyMethodObject;
调用 base_function 实例
我们指定了 __call__
的实现,用于 base_function
的实例。
检查 __objclass__
首先,如果函数的 __parent__
是一个类(回想一下,__objclass__
将成为 __parent__
的别名),则执行类型检查:如果 m_self
是 NULL
(对于扩展类型的未绑定方法就是这种情况),那么函数必须至少使用一个位置参数调用,并且第一个(通常称为 self
)必须是 __objclass__
的实例。如果不是,则引发 TypeError
。
请注意,绑定方法具有 m_self != NULL
,因此不会检查 __objclass__
。而是,在构造方法时会检查 __objclass__
。
标志
为了方便起见,我们定义了一个新常量:METH_CALLFLAGS
组合了来自 PyCFunctionDef.ml_flags
的所有标志,这些标志指定要调用的 C 函数的签名。它等于:
METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS | METH_PASS_FUNCTION
以上前四个标志中必须且只能设置一个标志,并且只有 METH_VARARGS
和 METH_FASTCALL
可以与 METH_KEYWORDS
组合。违反这些规则是未定义的行为。
还有一些新标志会影响函数调用,即 METH_PASS_FUNCTION
和 METH_CALL_UNBOUND
。一些标志已在 [5] 中记录。我们在下面解释其他标志。
自我切片
如果函数具有 m_self == NULL
并且标志 METH_CALL_UNBOUND
未设置,则从 *args
中移除第一个位置参数(如果有),并将其作为第一个参数传递给 C 函数。实际上,第一个位置参数将被视为 __self__
。这旨在支持未绑定方法,以便 C 函数无法区分绑定方法调用和未绑定方法调用。这不会以任何方式影响关键字参数。
这个过程称为自身切片,如果 m_self == NULL
并且 METH_CALL_UNBOUND
未设置,则称函数具有自身切片。
请注意,具有自身切片的 METH_NOARGS
函数实际上具有一个参数,即 self
。类似地,具有自身切片的 METH_O
函数具有两个参数。
METH_PASS_FUNCTION
如果设置了此标志,则 C 函数将以一个额外的第一个参数调用,即函数本身(base_function
实例)。作为特例,如果函数是 bound_method
,则传递方法的底层函数(但不递归:如果 bound_method
包裹了 bound_method
,则 __func__
仅应用一次)。
例如,一个普通的 METH_VARARGS
函数具有签名 (PyObject *self, PyObject *args)
。使用 METH_VARARGS | METH_PASS_FUNCTION
,这将变为 (PyObject *func, PyObject *self, PyObject *args)
。
METH_FASTCALL
这是一个现有的但未记录的标志。我们建议正式支持并记录它。
如果设置了标志 METH_FASTCALL
但未设置 METH_KEYWORDS
,则 ml_meth
字段的类型为 PyCFunctionFast
,它接受参数 (PyObject *self, PyObject *const *args, Py_ssize_t nargs)
。这样的函数只接受位置参数,并且这些参数作为长度为 nargs
的普通 C 数组 args
传递。
如果设置了标志 METH_FASTCALL | METH_KEYWORDS
,则 ml_meth
字段的类型为 PyCFunctionFastKeywords
,它接受参数 (PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
。位置参数作为长度为 nargs
的 C 数组 args
传递。关键字参数的值在该数组中紧随其后,从位置 nargs
开始。关键字参数的键(名称)作为 kwnames
中的 tuple
传递。例如,假设给出了 3 个位置参数和 2 个关键字参数。那么 args
是一个长度为 3 + 2 = 5 的数组,nargs
等于 3,kwnames
是一个 2 元组。
自动创建内置函数
Python 为扩展类型(使用 PyTypeObject.tp_methods
字段)和模块(使用 PyModuleDef.m_methods
字段)自动生成 cfunction
实例。数组 PyTypeObject.tp_methods
和 PyModuleDef.m_methods
必须是 PyMethodDef
结构的数组。
扩展类型的未绑定方法
未绑定方法的类型从 method_descriptor
变为 cfunction
。作为未绑定方法出现的对象与在类 __dict__
中出现的对象是同一个对象。Python 自动将 __parent__
属性设置为定义类。
模块的内置函数
对于模块的函数,__parent__
将设置为模块。除非给出标志 METH_BINDING
,否则 __self__
也将设置为模块(为了向后兼容)。
一个重要的结果是,默认情况下,这样的函数在用作属性时不会变为方法(base_function.__get__
只有在 m_self
为 NULL
时才会这样做)。有人可能会认为这是一个错误,但这样做是为了向后兼容的原因:在一个关于 python-ideas 的早期帖子中[6],大家一致同意保留内置函数的这个缺陷。
但是,为了允许对特定或新实现的内置函数这样做,标志 METH_BINDING
会阻止设置 __self__
。
进一步的改变
新的类型标志
一个新的 PyTypeObject
标志(用于 tp_flags
)被添加:Py_TPFLAGS_BASEFUNCTION
表示此类型的实例是函数,可以像 base_function
一样被调用和绑定为方法。
这不同于像 Py_TPFLAGS_LIST_SUBCLASS
这样的标志,因为它表示的不仅仅是子类:它还表示 __call__
和 __get__
的默认实现。特别是,base_function
的此类子类必须遵循部分 调用 base_function 实例 中的实现。
此标志会自动为从 base_function
继承 tp_call
和 tp_descr_get
实现的扩展类型设置。如果扩展类型以兼容的方式覆盖了 __call__
或 __get__
,则可以显式地指定它。标志 Py_TPFLAGS_BASEFUNCTION
绝不能为堆类型设置,因为这样做不安全(堆类型可以动态更改)。
C API 函数
我们列出了一些相关的 Python/C API 宏和函数。其中一些是现有的(可能已更改)函数,另一些是新的。
int PyBaseFunction_CheckFast(PyObject *op)
:如果op
是设置了Py_TPFLAGS_BASEFUNCTION
的类的实例,则返回 true。这是你需要用来确定访问base_function
内部是否有意义的函数。int PyBaseFunction_Check(PyObject *op)
:如果op
是base_function
的实例,则返回 true。PyObject *PyBaseFunction_New(PyTypeObject *cls, PyCFunctionDef *ml, PyObject *self, PyObject *module, PyObject *parent)
:从给定数据创建一个新的cls
实例(它必须是base_function
的子类)。int PyCFunction_Check(PyObject *op)
:如果op
是cfunction
的实例,则返回 true。int PyCFunction_NewEx(PyMethodDef* ml, PyObject *self, PyObject* module)
:创建一个新的cfunction
实例。作为特例,如果self
为NULL
,则改为设置self = Py_None
(为了向后兼容)。如果self
是一个模块,则__parent__
被设置为self
。否则,__parent__
为NULL
。- 对于许多现有的
PyCFunction_...
和PyMethod_
函数,我们定义了一个新的函数PyBaseFunction_...
,它作用于base_function
实例。旧函数被保留为新函数的别名。 int PyFunction_Check(PyObject *op)
:如果op
是设置了METH_PYTHON
标志的base_function
的实例,则返回 true(这等同于检查op
是否是function
的实例)。int PyFunction_CheckFast(PyObject *op)
:等同于PyFunction_Check(op) && PyBaseFunction_CheckFast(op)
。int PyFunction_CheckExact(PyObject *op)
:如果op
的类型是function
,则返回 true。PyObject *PyFunction_NewPython(PyTypeObject *cls, PyObject *code, PyObject *globals, PyObject *name, PyObject *qualname)
:从给定数据创建一个新的cls
实例(它必须是function
的子类)。PyObject *PyFunction_New(PyObject *code, PyObject *globals)
:创建一个新的function
实例。PyObject *PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname)
: 创建一个新的function
实例。PyObject *PyFunction_Copy(PyTypeObject *cls, PyObject *func)
: 通过复制给定的function
,创建一个新的cls
实例(必须是function
的子类)。
对 types 模块的更改
添加了两个类型:types.BaseFunctionType
对应于 base_function
,types.DefinedFunctionType
对应于 defined_function
。
除此之外,对 types
模块没有进行任何更改。特别是,types.FunctionType
指的是 function
。但是,实际类型将会发生变化:特别是,types.BuiltinFunctionType
将不再与 types.BuiltinMethodType
相同。
对 inspect 模块的更改
新函数 inspect.isbasefunction
检查 base_function
的实例。
inspect.isfunction
检查 defined_function
的实例。
inspect.isbuiltin
检查 cfunction
的实例。
inspect.isroutine
检查 isbasefunction
或 ismethoddescriptor
。
注意:bpo-33261 [3] 应该首先修复。
分析
目前,sys.setprofile
支持内建函数的 c_call
、c_return
和 c_exception
事件。当调用或从内建函数返回时,会生成这些事件。相比之下,call
和 return
事件是由函数本身生成的。所以,对于 call
和 return
事件不需要更改任何内容。
由于我们不再区分 C 函数和 Python 函数,我们需要防止 Python 函数的 c_*
事件。如果 ml_flags
中设置了 METH_PYTHON
标志,则通过不生成这些事件来实现这一点。
非 CPython 实现
这个 PEP 的大部分内容只与 CPython 相关。对于其他 Python 实现,需要进行的两个更改是 base_function
基类,以及 function
可以被子类化的事实。类 cfunction
和 defined_function
不是必需的。
我们要求 base_function
保持一致性,但我们对其没有要求:如果它只是 object
的副本是可以接受的。不需要支持新的 __parent__
(和 __objclass__
)属性。如果没有 defined_function
类,那么 types.DefinedFunctionType
应该成为 types.FunctionType
的别名。
基本原理
为什么不直接更改现有类?
有人可能会尝试通过保留现有的类来解决这个问题,而不引入新的 base_function
类。
这看起来可能是一个更简单的解决方案,但事实并非如此:它需要对 3 个不同的类进行内省支持:function
、builtin_function_or_method
和 method_descriptor
。对于后两个类,“内省支持”至少意味着允许子类化。但我们不想失去性能,所以我们需要快速子类检查。这将需要在 tp_flags
中添加两个新的标志。我们希望子类允许内建函数的 __get__
,所以我们也应该为内建函数实现 LOAD_METHOD
操作码。更一般地说,很多功能都需要重复,最终的结果将是更复杂的代码。
也不清楚内建函数子类的内省将如何与 __text_signature__
交互。在同一个类上拥有两种独立的 inspect.signature
支持,听起来像是自找麻烦。
而且这并不能解决在 动机 中提到的内建函数和 Python 函数之间的一些其他差异。
为什么 __text_signature__ 不是解决方案
内建函数有一个属性 __text_signature__
,它以纯文本形式给出函数的签名。默认值由 ast.literal_eval
评估。因此,它只支持少量标准 Python 类,而不是任意 Python 对象。
即使 __text_signature__
以某种方式允许任意签名,那也只是一部分内省:例如,它对 inspect.getsourcefile
没有任何帮助。
defined_function 与 function
在很多地方,需要决定是将旧的 function
类替换为 defined_function
还是新的 function
类。这是通过考虑最可能的用例来决定的。
types.FunctionType
指的是function
,因为该类型可能用于使用types.FunctionType(...)
来构造实例。inspect.isfunction()
指的是defined_function
,因为这是支持内省的类。- C API 函数必须引用
function
,因为我们没有指定如何实现defined_function
的各种属性。我们预计这不会成为问题,因为通常没有理由让 C 扩展进行内省。
本 PEP 的范围:哪些类参与其中?
这个 PEP 的主要动机是修复函数类,所以我们当然希望统一现有的类 builtin_function_or_method
和 function
。
由于内建函数和方法具有相同的类,因此将绑定方法包括在内似乎很自然。并且由于 Python 函数没有“未绑定方法”,因此删除扩展类型的未绑定方法是有意义的。
目前,对类 staticmethod
、classmethod
和 classmethod_descriptor
没有进行任何更改。将这些类放在 base_function
类层次结构中并统一 classmethod
和 classmethod_descriptor
肯定是有意义的。但是,这个 PEP 已经足够大,所以将其留作未来可能的改进。
扩展类型的槽包装器,如 __init__
或 __eq__
,与普通方法有很大不同。它们通常也不会被直接调用,因为你通常会编写 foo[i]
而不是 foo.__getitem__(i)
。所以这些内容不在本 PEP 的范围之内。
Python 还有一个 instancemethod
类,它似乎是 Python 2 的遗留物,在 Python 2 中它用于绑定和未绑定方法。目前还不清楚它是否还有用例。无论如何,在这个 PEP 中没有理由处理它。
待办事项:instancemethod
应该被弃用吗?它似乎在 CPython 3.7 中没有被使用,但也许外部包使用了它?
不处理 METH_STATIC 和 METH_CLASS
这个 PEP 中几乎没有内容涉及标志 METH_STATIC
和 METH_CLASS
。这些标志只被 内建函数的自动创建 检查。当 staticmethod
、classmethod
或 classmethod_descriptor
被绑定(即调用 __get__
)时,会创建一个 base_function
实例,其中 m_self != NULL
。对于 classmethod
,这是显而易见的,因为 m_self
是方法绑定的类。对于 staticmethod
,可以为 m_self
选择任意 Python 对象。为了向后兼容性,我们为扩展类型的静态方法选择 m_self = __parent__
。
base_function 中的 __self__
乍一看,在 base_function
中添加 __self__
槽而不是在 bound_method
中添加,可能看起来很奇怪。我们从现有的 builtin_function_or_method
类中获得了这个想法。它允许我们对本 PEP 中讨论的各种函数类拥有 __call__
和 __get__
的单一通用实现。
它还使得为现有的内建函数设置 __self__
为模块变得很容易(例如,sys.exit.__self__
是 sys
)。
__doc__ 的两种实现
base_function
不支持函数文档字符串。相反,cfunction
和 function
类分别有自己的处理文档字符串的方式(而 bound_method
只是从包装的函数中获取 __doc__
)。
对于 cfunction
,文档字符串(连同文本签名)被存储为 PyMethodDef
的只读 ml_doc
字段中的 C 字符串。对于 function
,文档字符串被存储为可写的 Python 对象,并且实际上不需要是字符串。看起来很难统一这两种处理 __doc__
的完全不同的方法。为了向后兼容,我们保留了现有的实现。
对于 defined_function
,我们要求实现 __doc__
,但我们没有指定如何实现。子类可以像 cfunction
一样实现 __doc__
,或者使用结构成员或其他方式。
子类化
我们禁止对 cfunction
和 bound_method
进行子类化,以允许对 PyCFunction_Check
和 PyMethod_Check
进行快速类型检查。
我们允许对其他类进行子类化,因为没有理由禁止它。对于 Python 模块,唯一相关的子类化类是 function
,因为其他类无法实例化。
替换 tp_call:METH_PASS_FUNCTION 和 METH_CALL_UNBOUND
新的标志 METH_PASS_FUNCTION
和 METH_CALL_UNBOUND
旨在支持以前使用自定义 tp_call
的情况。它减少了 Python/ceval.c
中用于调用对象的特殊快速路径的数量:与其分别处理 Python 函数、内置函数和方法描述符,不如只进行一次检查。
tp_call
的签名本质上是 PyBaseFunctionObject.m_ml.ml_meth
的签名,带有标志 METH_VARARGS | METH_KEYWORDS | METH_PASS_FUNCTION | METH_CALL_UNBOUND
(唯一的区别是添加了一个 self
参数)。因此,应该很容易将现有的 tp_call
槽改为使用 base_function
实现。
在 C 函数只需要访问函数的额外元数据(例如 __parent__
)的情况下,使用 METH_PASS_FUNCTION
但不使用 METH_CALL_UNBOUND
也有意义。例如,这需要支持 PEP 573。将现有方法转换为使用 METH_PASS_FUNCTION
很简单:只需要在 C 函数中添加一个额外的参数。
向后兼容性
在设计这个 PEP 时,非常注意不要过分破坏向后兼容性。大多数可能不兼容的更改是对 CPython 实现细节的更改,这些细节在其他 Python 解释器中是不同的。特别是,在 PyPy 上正确运行的 Python 代码很可能继续与这个 PEP 一起工作。
标准类和函数,例如 staticmethod
、functools.partial
或 operator.methodcaller
,根本不需要更改。
对 types 和 inspect 的更改
对 types
和 inspect
提出的更改旨在最大限度地减少行为上的更改。然而,不可避免地会有一些东西发生变化,这会导致使用 types
或 inspect
的代码崩溃。例如,在 Python 标准库中,由于这一点,doctest
模块需要进行更改。
此外,将各种类型的函数作为输入的工具将需要处理新的函数层次结构以及自定义函数类的可能性。
Python 函数
对于 Python 函数,基本上没有什么变化。以前存在的属性仍然存在,Python 函数可以像以前一样初始化、调用和转换为方法。
名称 function
保持为向后兼容。虽然将名称更改为更具体的名称(例如 python_function
)可能更有意义,但这将需要在文档和测试套件中进行大量令人厌烦的更改。
模块的内置函数
对于内置函数,也没有任何变化。我们保留了这些函数不绑定为方法的旧行为。这是 __self__
设置为模块的结果。
内置绑定和未绑定方法
内置绑定方法和未绑定方法的类型将发生变化。但是,这不会影响调用这些方法,因为 base_function.__call__
中的协议(特别是 __objclass__
和自身切片的处理)专门设计为向后兼容。所有以前存在的属性(如 __objclass__
和 __self__
)仍然存在。
新属性
一些对象获得新的特殊双下划线属性。例如,新的属性 __parent__
出现在所有内置函数上,所有方法都获得 __func__
属性。__self__
现在是 Python 函数的特殊只读属性,这一事实导致了 [4] 中的麻烦。一般来说,我们预计不会造成太多破坏。
method_descriptor 和 PyDescr_NewMethod
类 method_descriptor
和构造函数 PyDescr_NewMethod
应该被弃用。它们不再被 CPython 本身使用,但仍然得到支持。
两阶段实施
待办事项:本节是可选的。如果这个 PEP 被接受,应该决定是否采用这种两阶段实现。
如上所述,对 types 和 inspect 的更改 会破坏一些现有代码。为了进一步减少破坏,这个 PEP 可以分两个阶段实现。
第一阶段:保留现有类,但添加基类
最初,实现 base_function
类并将其用作通用基类,但除此之外保留现有类(但不保留它们的实现)。
在这个提议中,类层次结构将变为
object
|
|
base_function
/ | \
/ | \
/ | \
cfunction | defined_function
| | | \
| | bound_method \
| | \
| method_descriptor function
|
builtin_function_or_method
叶子类 builtin_function_or_method
、method_descriptor
、bound_method
和 function
对应于现有类(将 method
重命名为 bound_method
)。
在模块中自动创建的函数成为 builtin_function_or_method
的实例。扩展类型的未绑定方法成为 method_descriptor
的实例。
类 method_descriptor
是 cfunction
的副本,除了 __get__
返回 builtin_function_or_method
而不是 bound_method
。
类 builtin_function_or_method
与 bound_method
具有相同的 C 结构,但它继承自 cfunction
。属性 __func__
不是必需的:它只在绑定 method_descriptor
时定义。
我们保留 inspect
函数的实现原样。由于这一点以及保留了现有类,所以对于进行类型检查的代码来说,向后兼容性得到了保证。
由于显示实际的 DeprecationWarning
会影响很多正确运行的代码,因此任何弃用都只会出现在文档中。另一个原因是,很难显示调用 isinstance(x, t)
的警告(但可以使用 __instancecheck__
技巧完成),而对于 type(x) is t
来说是不可能的。
第二阶段
第二阶段是这个 PEP 中其余部分实际描述的内容。就实现而言,与第一阶段相比,它将是一个相对较小的更改。
参考实现
这个 PEP 的大部分内容已经在 CPython 中实现了,网址为 https://github.com/jdemeyer/cpython/tree/pep575
有四个步骤,对应于该分支上的提交。在每个步骤之后,CPython 处于大部分工作状态。
- 添加
base_function
类并使其成为cfunction
的子类。这是迄今为止最大的步骤,因为完整的__call__
协议在此步骤中实现。 - 将
method
重命名为bound_method
并使其成为base_function
的子类。将扩展类型的未绑定方法更改为cfunction
的实例,以便扩展类型的绑定方法也是bound_method
的实例。 - 实现
defined_function
和function
。 - 对 Python 的其他部分进行更改,例如标准库和测试套件。
附录:当前情况
注意:本节在 PEP 草案阶段更有用,因此如果 PEP 被接受,请随时将其删除。
为了便于参考,我们详细描述了 CPython 3.7 中相关的现有类。
每个涉及的类都是一个“孤儿”类(没有非平凡的子类或超类)。
builtin_function_or_method:内置函数和绑定方法
这些是类型 PyCFunction_Type,结构为 PyCFunctionObject。
typedef struct {
PyObject_HEAD
PyMethodDef *m_ml; /* Description of the C function to call */
PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */
PyObject *m_module; /* The __module__ attribute, can be anything */
PyObject *m_weakreflist; /* List of weak references */
} PyCFunctionObject;
struct PyMethodDef {
const char *ml_name; /* The name of the built-in function/method */
PyCFunction ml_meth; /* The C function that implements it */
int ml_flags; /* Combination of METH_xxx flags, which mostly
describe the args expected by the C func */
const char *ml_doc; /* The __doc__ attribute, or NULL */
};
其中 PyCFunction
是一个 C 函数指针(有多种形式,最基本的形式接受两个参数:self
和 *args
)。
此类用于函数和绑定方法:对于方法,m_self
槽指向对象。
>>> dict(foo=42).get
<built-in method get of dict object at 0x...>
>>> dict(foo=42).get.__self__
{'foo': 42}
在某些情况下,函数被认为是定义它的模块的“方法”。
>>> import os
>>> os.kill
<built-in function kill>
>>> os.kill.__self__
<module 'posix' (built-in)>
method_descriptor:内置未绑定方法
这些是类型 PyMethodDescr_Type,结构为 PyMethodDescrObject。
typedef struct {
PyDescrObject d_common;
PyMethodDef *d_method;
} PyMethodDescrObject;
typedef struct {
PyObject_HEAD
PyTypeObject *d_type;
PyObject *d_name;
PyObject *d_qualname;
} PyDescrObject;
function:Python 函数
这些是类型 PyFunction_Type,结构为 PyFunctionObject。
typedef struct {
PyObject_HEAD
PyObject *func_code; /* A code object, the __code__ attribute */
PyObject *func_globals; /* A dictionary (other mappings won't do) */
PyObject *func_defaults; /* NULL or a tuple */
PyObject *func_kwdefaults; /* NULL or a dict */
PyObject *func_closure; /* NULL or a tuple of cell objects */
PyObject *func_doc; /* The __doc__ attribute, can be anything */
PyObject *func_name; /* The __name__ attribute, a string object */
PyObject *func_dict; /* The __dict__ attribute, a dict or NULL */
PyObject *func_weakreflist; /* List of weak references */
PyObject *func_module; /* The __module__ attribute, can be anything */
PyObject *func_annotations; /* Annotations, a dict or NULL */
PyObject *func_qualname; /* The qualified name */
/* Invariant:
* func_closure contains the bindings for func_code->co_freevars, so
* PyTuple_Size(func_closure) == PyCode_GetNumFree(func_code)
* (func_closure may be NULL if PyCode_GetNumFree(func_code) == 0).
*/
} PyFunctionObject;
在 Python 3 中,没有“未绑定方法”类:未绑定方法只是一个普通函数。
method:Python 绑定方法
这些是类型 PyMethod_Type,结构为 PyMethodObject。
typedef struct {
PyObject_HEAD
PyObject *im_func; /* The callable object implementing the method */
PyObject *im_self; /* The instance it is bound to */
PyObject *im_weakreflist; /* List of weak references */
} PyMethodObject;
参考文献
版权
本文件已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0575.rst