PEP 580 – C 调用协议
- 作者:
- Jeroen Demeyer <J.Demeyer at UGent.be>
- BDFL 代表:
- Petr Viktorin
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建:
- 2018年6月14日
- Python 版本:
- 3.8
- 历史记录:
- 2018年6月20日,2018年6月22日,2018年7月16日
拒绝通知
此 PEP 被拒绝,转而支持 PEP 590,该提案为可调用对象提供了一个更简单的公共 C API。
摘要
提出了一种新的“C 调用”协议。它适用于表示需要实现快速调用的函数或方法的类。目标是将所有现有的内置函数优化推广到任意扩展类型。
在参考实现中,此新协议用于现有的类 builtin_function_or_method
和 method_descriptor
。但是,将来可能会有更多类实现它。
注意:此 PEP 仅处理 Python/C API,它不影响 Python 语言或标准库。
动机
标准函数/方法类 builtin_function_or_method
和 method_descriptor
允许非常高效地调用 C 代码。但是,它们不可子类化,这使得它们不适合许多应用程序:例如,它们提供有限的自省支持(仅使用 __text_signature__
的签名,没有任意的 __qualname__
,没有 inspect.getfile()
)。也不可能存储其他数据来实现诸如 functools.partial
或 functools.lru_cache
之类的东西。因此,用户有很多理由希望在 C 中实现自定义函数/方法类(从鸭子类型意义上讲)。不幸的是,此类自定义类必然比标准 CPython 函数类慢:字节码解释器具有各种针对 builtin_function_or_method
、method_descriptor
、method
和 function
实例的特定优化。
此 PEP 还允许简化现有代码:检查 builtin_function_or_method
和 method_descriptor
可以简单地替换为检查和使用 C 调用协议。未来的 PEP 可能会为更多类实现 C 调用协议,从而实现进一步的简化。
我们还设计了 C 调用协议,以便将来可以轻松地扩展新功能。
有关更多背景和动机,请参阅 PEP 579。
概述
当前,CPython 对几个特定函数类进行了多种快速调用的优化。一个很好的例子是操作码 CALL_FUNCTION
的实现,其结构如下(查看实际代码)
if (PyCFunction_Check(func)) {
return _PyCFunction_FastCallKeywords(func, stack, nargs, kwnames);
}
else if (Py_TYPE(func) == &PyMethodDescr_Type) {
return _PyMethodDescr_FastCallKeywords(func, stack, nargs, kwnames);
}
else {
if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
/* ... */
}
if (PyFunction_Check(func)) {
return _PyFunction_FastCallKeywords(func, stack, nargs, kwnames);
}
else {
return _PyObject_FastCallKeywords(func, stack, nargs, kwnames);
}
}
使用 tp_call
槽调用这些特殊情况类的实例比使用优化慢。此 PEP 的基本思想是为用户 C 代码启用此类优化,作为调用方和被调用方。
现有的类 builtin_function_or_method
和其他一些类使用 PyMethodDef
结构来描述底层 C 函数及其签名。第一个具体更改是用新的结构 PyCCallDef
替换它。它存储与 PyMethodDef
相同的一些信息,但有一个重要的补充:函数的“父对象”(定义它的类或模块)。请注意,PyMethodDef
数组仍用于构造函数/方法,但不再用于调用它们。
其次,我们希望每个类都可以使用此类 PyCCallDef
来优化调用,因此 PyTypeObject
结构获得了一个 tp_ccalloffset
字段,该字段给出对象结构中 PyCCallDef *
的偏移量,以及一个标志 Py_TPFLAGS_HAVE_CCALL
,指示 tp_ccalloffset
有效。
第三,由于我们希望有效地处理未绑定和绑定的方法(而不是仅处理普通函数),因此我们需要在协议中处理 __self__
:在对象结构中的 PyCCallDef *
之后,有一个 PyObject *self
字段。这两个字段一起被称为 PyCCallRoot
结构。
使用这些新结构高效调用对象的新协议称为“C 调用协议”。
注意:在此 PEP 中,“未绑定方法”和“绑定方法”指的是通用行为,而不是特定类。例如,在应用 __get__
后,未绑定方法将变成绑定方法。
新的数据结构
PyTypeObject
结构获得了一个新的字段 Py_ssize_t tp_ccalloffset
和一个新的标志 Py_TPFLAGS_HAVE_CCALL
。如果设置了此标志,则假定 tp_ccalloffset
是对象结构内的一个有效偏移量(类似于 tp_dictoffset
和 tp_weaklistoffset
)。它必须是一个严格的正整数。在该偏移量处,会出现一个 PyCCallRoot
结构
typedef struct {
const PyCCallDef *cr_ccall;
PyObject *cr_self; /* __self__ argument for methods */
} PyCCallRoot;
PyCCallDef
结构包含描述如何调用函数所需的一切
typedef struct {
uint32_t cc_flags;
PyCFunc cc_func; /* C function to call */
PyObject *cc_parent; /* class or module */
} PyCCallDef;
将 __self__
放置在 PyCCallDef
之外的原因是 PyCCallDef
不打算在创建函数后更改。单个 PyCCallDef
可以由未绑定方法和多个绑定方法共享。如果我们将 __self__
放入该结构中,这将无法实现。
注意:与 tp_dictoffset
不同,我们不允许 tp_ccalloffset
为负数表示从末尾开始计数。似乎没有使用案例,这只会使实现复杂化。
父对象
cc_parent
字段(例如通过 Python 代码中的 __parent__
或 __objclass__
描述符访问)可以是任何 Python 对象或 NULL。自定义类可以自由地将 cc_parent
设置为它们想要的任何值。只有在设置了 CCALL_OBJCLASS
标志时,C 调用协议才会使用它。
对于扩展类型的成员方法,cc_parent
指向定义该方法的类(可能是 type(self)
的超类)。目前从方法的代码中检索它并非易事。将来,这可用于通过定义类访问模块状态。有关详细信息,请参阅 PEP 573 的基本原理。
当设置了 CCALL_OBJCLASS
标志(对于扩展类型的成员方法将设置此标志)时,cc_parent
用于执行以下类型的检查
>>> list.append({}, "x")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor 'append' requires a 'list' object but received a 'dict'
对于模块的函数,cc_parent
设置为模块。目前,这与 __self__
完全相同。但是,将 __self__
用于模块是当前实现的一个特性:将来,我们希望允许以正常方式使用 __self__
来实现方法的函数。此类函数仍然可以使用 cc_parent
来代替引用模块。
父对象通常还用于实现 __qualname__
。新的 C API 函数 PyCCall_GenericGetQualname()
正是这样做的。
使用 tp_print
我们建议用 tp_ccalloffset
替换现有的未使用字段 tp_print
。由于 Py_TPFLAGS_HAVE_CCALL
不会添加到 Py_TPFLAGS_DEFAULT
中,因此可以确保对于设置 tp_print
的现有扩展模块完全向后兼容。这也意味着当指定了 Py_TPFLAGS_HAVE_CCALL
时,我们可以要求 tp_ccalloffset
是一个有效的偏移量:我们不需要检查 tp_ccalloffset != 0
。在未来的 Python 版本中,我们可能会决定 tp_print
无条件地变为 tp_ccalloffset
,删除 Py_TPFLAGS_HAVE_CCALL
标志,而是检查 tp_ccalloffset != 0
。
注意:PyTypeObject
的确切布局不是稳定 ABI 的一部分(参见 PEP 384 – 定义稳定 ABI)。因此,将 tp_print
字段从 printfunc
(一个函数指针)更改为 Py_ssize_t
应该不会有问题,即使这改变了 PyTypeObject
结构的内存布局。此外,在所有通常构建二进制文件的系统(Windows、Linux、macOS)上,printfunc
和 Py_ssize_t
的大小相同,因此二进制兼容性问题根本不会出现。
C 调用协议
我们说一个类实现了 C 调用协议,如果它设置了 Py_TPFLAGS_HAVE_CCALL
标志(如上所述,它必须随后设置 tp_ccalloffset > 0
)。这样的类必须实现 __call__
,如本节所述(在实践中,这仅仅意味着将 tp_call
设置为 PyCCall_Call
)。
cc_func
字段是一个 C 函数指针,它与 PyMethodDef
的现有 ml_meth
字段的作用相同。它的精确签名取决于标志。影响 cc_func
签名的标志子集由位掩码 CCALL_SIGNATURE
给出。以下是 cc_flags & CCALL_SIGNATURE
的可能值以及 C 函数所接受的参数。返回值始终为 PyObject *
。以下是现有 PyMethodDef
签名标志的类似物
CCALL_VARARGS
:cc_func(PyObject *self, PyObject *args)
CCALL_VARARGS | CCALL_KEYWORDS
:cc_func(PyObject *self, PyObject *args, PyObject *kwds)
(kwds
为NULL
或字典;此字典不得被被调用者修改)CCALL_FASTCALL
:cc_func(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
CCALL_FASTCALL | CCALL_KEYWORDS
:cc_func(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
(kwnames
为NULL
或关键字名称的非空元组)CCALL_NOARGS
:cc_func(PyObject *self, PyObject *unused)
(第二个参数始终为NULL
)CCALL_O
:cc_func(PyObject *self, PyObject *arg)
标志 CCALL_DEFARG
可以与任何这些组合。如果是这样,C 函数会在 self
之前作为第一个参数接受一个额外的参数,即指向用于此调用的 PyCCallDef
结构的常量指针。例如,我们有以下签名
CCALL_DEFARG | CCALL_VARARGS
:cc_func(const PyCCallDef *def, PyObject *self, PyObject *args)
一个例外是 CCALL_DEFARG | CCALL_NOARGS
:unused
参数被丢弃,因此签名变为
CCALL_DEFARG | CCALL_NOARGS
:cc_func(const PyCCallDef *def, PyObject *self)
注意:与现有的 METH_...
标志不同,CCALL_...
常量不一定表示单个位。因此,检查 if (cc_flags & CCALL_VARARGS)
不是检查签名的有效方法。这些标志在 Python 版本之间也没有二进制兼容性的保证。这允许实现选择标志最有效的数值。在参考实现中,cc_flags & CCALL_SIGNATURE
的合法值恰好构成区间 [0,…,11]。这意味着编译器可以使用计算 goto 轻松优化这些情况下的 switch
语句。
检查 __objclass__
如果设置了 CCALL_OBJCLASS
标志并且 cr_self
为 NULL(对于扩展类型的未绑定方法就是这种情况),则会执行类型检查:函数必须至少用一个位置参数调用,并且第一个(通常称为 self
)必须是 cc_parent
(它必须是一个类)的实例。如果不是,则会引发 TypeError
。
自身切片
如果 cr_self
不为 NULL 或 cc_flags
中未设置标志 CCALL_SELFARG
,则作为 self
传递的参数只是 cr_self
。
如果 cr_self
为 NULL 且设置了标志 CCALL_SELFARG
,则从 args
中删除第一个位置参数,并将其作为 self
参数传递给 C 函数。实际上,第一个位置参数被视为 __self__
。如果没有位置参数,则会引发 TypeError
。
此过程称为“self 切片”,如果 cr_self
为 NULL 且设置了 CCALL_SELFARG
,则称函数具有 self 切片。
请注意,具有 self 切片的 CCALL_NOARGS
函数实际上有一个参数,即 self
。类似地,具有 self 切片的 CCALL_O
函数有两个参数。
描述符行为
支持 C 调用协议的类必须以特定方式实现描述符协议。
这是为了有效地实现绑定方法:如果其他代码可以对 __get__
的行为做出假设,则可以实现否则不可能实现的优化。特别是,我们希望允许在绑定方法和未绑定方法之间共享 PyCCallDef
结构。我们还需要正确实现 _PyObject_GetMethod
,它由 LOAD_METHOD
/CALL_METHOD
优化使用。
首先,如果 func
支持 C 调用协议,则不得实现 func.__set__
和 func.__delete__
。
其次,func.__get__
必须按如下方式执行
- 如果
cr_self
不为 NULL,则__get__
必须是无操作的,这意味着func.__get__(obj, cls)(*args, **kwds)
的行为与func(*args, **kwds)
完全相同。也可以不实现__get__
。 - 如果
cr_self
为 NULL,则func.__get__(obj, cls)(*args, **kwds)
(其中obj
不为 None)必须等效于func(obj, *args, **kwds)
。特别是,在这种情况下必须实现__get__
。这与 self 切片 无关:obj
可以作为self
参数传递给 C 函数,也可以作为第一个位置参数传递。 - 如果
cr_self
为 NULL,则func.__get__(None, cls)(*args, **kwds)
必须等效于func(*args, **kwds)
。
对象 func.__get__(obj, cls)
没有限制。例如,后者不需要实现 C 调用协议。我们只指定 func.__get__(obj, cls).__call__
的作用。
对于完全不关心 __self__
和 __get__
的类,最简单的解决方案是赋值 cr_self = Py_None
(或任何其他非 NULL 值)。
__name__ 属性
C 调用协议要求该函数具有一个 __name__
属性,该属性的类型为 str
(而不是子类)。
此外,由__name__
返回的对象必须存储在某个地方;它不能是一个临时对象。这是必需的,因为PyEval_GetFuncName
使用对__name__
属性的借用引用(另请参见[2])。
通用 API 函数
本节列出了处理 C 调用协议的新公共 API 函数或宏。
int PyCCall_Check(PyObject *op)
:如果op
实现了 C 调用协议,则返回 true。
以下所有函数和宏都适用于支持 C 调用协议的任何实例。换句话说,PyCCall_Check(func)
必须为 true。
PyObject *PyCCall_Call(PyObject *func, PyObject *args, PyObject *kwds)
:使用位置参数args
和关键字参数kwds
调用func
(kwds
可以为 NULL)。此函数旨在放置在tp_call
槽中。PyObject *PyCCall_FastCall(PyObject *func, PyObject *const *args, Py_ssize_t nargs, PyObject *kwds)
:使用由args[0]
、…、args[nargs-1]
给出的nargs
个位置参数调用func
。参数kwds
可以为 NULL(无关键字参数)、一个包含name:value
项的字典或一个包含关键字名称的元组。在后一种情况下,关键字值存储在args
数组中,从args[nargs]
开始。
访问PyCCallRoot
和PyCCallDef
结构的宏
const PyCCallRoot *PyCCall_CCALLROOT(PyObject *func)
:指向func
内部的PyCCallRoot
结构的指针。const PyCCallDef *PyCCall_CCALLDEF(PyObject *func)
:PyCCall_CCALLROOT(func)->cr_ccall
的简写。uint32_t PyCCall_FLAGS(PyObject *func)
:PyCCall_CCALLROOT(func)->cr_ccall->cc_flags
的简写。PyObject *PyCCall_SELF(PyOject *func)
:PyCCall_CCALLROOT(func)->cr_self
的简写。
通用获取器,旨在放入tp_getset
数组中
PyObject *PyCCall_GenericGetParent(PyObject *func, void *closure)
:返回cc_parent
。如果cc_parent
为 NULL,则引发AttributeError
。PyObject *PyCCall_GenericGetQualname(PyObject *func, void *closure)
:返回一个适合用作__qualname__
的字符串。如果可能,这将使用cc_parent
的__qualname__
。它还使用__name__
属性。
性能分析
仅当调用builtin_function_or_method
或method_descriptor
的实际实例时,才会生成概要分析事件c_call
、c_return
和c_exception
。这样做是为了简单起见,也为了向后兼容(这样概要分析函数就不会接收它无法识别的对象)。在未来的 PEP 中,我们可能会将 C 级概要分析扩展到实现 C 调用协议的任意类。
对内置函数和方法的更改
此 PEP 的参考实现将现有的类builtin_function_or_method
和method_descriptor
更改为使用 C 调用协议。实际上,这两个类几乎合并了:实现变得非常相似,但它们仍然是单独的类(主要是为了向后兼容)。PyCCallDef
结构只是作为对象结构的一部分存储。这两个类都使用PyCFunctionObject
作为对象结构。这是这两个类的新的布局
typedef struct {
PyObject_HEAD
PyCCallDef *m_ccall;
PyObject *m_self; /* Passed as 'self' arg to the C function */
PyCCallDef _ccalldef; /* Storage for m_ccall */
PyObject *m_name; /* __name__; str object (not NULL) */
PyObject *m_module; /* __module__; can be anything */
const char *m_doc; /* __text_signature__ and __doc__ */
PyObject *m_weakreflist; /* List of weak references */
} PyCFunctionObject;
对于模块的函数和扩展类型的未绑定方法,m_ccall
指向_ccalldef
字段。对于绑定方法,m_ccall
指向未绑定方法的PyCCallDef
。
**注意**:method_descriptor
的新布局将其更改为不再以PyDescr_COMMON
开头。这纯粹是一个实现细节,应该会导致很少(如果有的话)的兼容性问题。
C API 函数
添加了以下函数(也添加到稳定 ABI中)
PyObject * PyCFunction_ClsNew(PyTypeObject *cls, PyMethodDef *ml, PyObject *self, PyObject *module, PyObject *parent)
:创建一个新的对象,其对象结构为PyCFunctionObject
,类为cls
。PyMethodDef
结构的条目用于构造新对象,但不会存储指向PyMethodDef
结构的指针。C 调用协议的标志根据ml->ml_flags
、self
和parent
自动确定。
现有的函数PyCFunction_New
、PyCFunction_NewEx
和PyDescr_NewMethod
都是根据PyCFunction_ClsNew
实现的。
未记录的函数PyCFunction_GetFlags
和PyCFunction_GET_FLAGS
已弃用。它们仍然通过将原始METH_...
标志存储在cc_flags
内部的位字段中来人为地支持。尽管PyCFunction_GetFlags
在技术上是稳定 ABI的一部分,但它不太可能以这种方式使用:首先,它甚至没有记录在案。其次,标志METH_FASTCALL
不是稳定 ABI 的一部分,但它非常常见(因为 Argument Clinic)。因此,如果无法支持METH_FASTCALL
,很难想象PyCFunction_GetFlags
的用例。PyCFunction_GET_FLAGS
和PyCFunction_GetFlags
根本没有被 CPython 在Objects/call.c
之外使用,这进一步表明这些函数并不是特别有用。
继承
扩展类型继承基类的类型标志Py_TPFLAGS_HAVE_CCALL
和值tp_ccalloffset
,前提是它们以与基类相同的方式实现tp_call
和tp_descr_get
。堆类型永远不会继承 C 调用协议,因为那是不安全的(堆类型可以动态更改)。
性能
此 PEP 不应影响现有代码的性能(无论是正面的还是负面的)。它旨在允许编写高效的新代码,而不是使现有代码更快。
以下是一些指向python-dev
邮件列表的指针,其中讨论了性能改进
稳定的 ABI
函数PyCFunction_ClsNew
已添加到稳定 ABI中。
与 C 调用协议相关的任何函数、结构或常量均未添加到稳定 ABI 中。
有两个原因:首先,C 调用协议最有用的功能可能是METH_FASTCALL
调用约定。鉴于这甚至不是公共 API 的一部分(另请参见PEP 579,问题 6),将 C 调用协议中的任何其他内容添加到稳定 ABI 中将很奇怪。
其次,我们希望 C 调用协议在将来可扩展。通过不向稳定 ABI 添加任何内容,我们可以在不受限制的情况下做到这一点。
向后兼容性
Python 接口或已记录的 C API 没有任何区别(在所有函数都以相同的功能继续受支持的意义上)。
唯一潜在的破坏是访问PyCFunctionObject
和PyMethodDescrObject
内部的 C 代码。我们预计由于此原因而产生的问题很少。
基本原理
为什么这比 PEP 575 更好?
关于PEP 575的一个主要抱怨是,它将功能(调用和内省协议)与类层次结构耦合在一起:只有当一个类是base_function
的子类时,才能从新特性中受益。对于现有的类来说,这样做可能很困难,因为它们可能对C对象结构的布局有其他约束,这些约束来自现有的基类或实现细节。例如,functools.lru_cache
无法按原样实现PEP 575。
它还使实现变得复杂,正是因为在实现细节和类层次结构中都需要进行更改。
当前的PEP没有这些问题。
为什么将函数指针存储在实例中?
调用对象所需的信息存储在实例中(在PyCCallDef
结构中),而不是类中。这与tp_call
槽或早期尝试实现tp_fastcall
槽[1]不同。
主要用例是内置函数和方法。对于这些函数,要调用的C函数确实取决于实例。
请注意,当前协议使支持所有实例都调用相同C函数的情况变得很容易:只需对每个实例使用一个静态的PyCCallDef
结构即可。
为什么使用 CCALL_OBJCLASS?
标志CCALL_OBJCLASS
旨在支持各种情况下需要检查self
参数的类的情况,例如
>>> list.append({}, None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: append() requires a 'list' object but received a 'dict'
>>> list.__len__({})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__len__' requires a 'list' object but received a 'dict'
>>> float.__dict__["fromhex"](list, "0xff")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor 'fromhex' for type 'float' doesn't apply to type 'list'
在参考实现中,只有第一个使用了新代码。其他示例表明,此类检查出现在多个位置,因此添加对它们的通用支持是有意义的。
为什么使用 CCALL_SELFARG?
标志CCALL_SELFARG
和自切片的概念是支持方法所必需的:C函数不应该关心它是作为非绑定方法还是绑定方法被调用。在这两种情况下,都应该有一个self
参数,它只是非绑定方法调用的第一个位置参数。
例如,list.append
是一个METH_O
方法。调用list.append([], 42)
和[].append(42)
都应该转换为C调用list_append([], 42)
。
借助提议的C调用协议,我们可以以这样一种方式支持这一点:非绑定方法和绑定方法都共享一个PyCCallDef
结构(设置了CCALL_SELFARG
标志)。
因此,CCALL_SELFARG
有两个优点:调用方法没有额外的间接层,并且构造绑定方法不需要设置PyCCallDef
结构。
另一个次要优点是,我们可以使错误调用签名的错误消息在Python方法和内置方法之间更加统一。在以下示例中,Python无法确定方法是接受1个还是2个参数
>>> class List(list):
... def myappend(self, item):
... self.append(item)
>>> List().myappend(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: myappend() takes 2 positional arguments but 3 were given
>>> List().append(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: append() takes exactly one argument (2 given)
目前,PyCFunction_Call
无法知道用户可见参数的实际数量,因为它无法在运行时区分函数(没有self
参数)和绑定方法(有self
参数)。CCALL_SELFARG
标志使这种差异变得明确。
为什么使用 CCALL_DEFARG?
标志CCALL_DEFARG
使被调用者可以访问PyCCallDef *
。这有各种用例
- 被调用者可以使用
cc_parent
字段,这对于PEP 573很有用。 - 应用程序可以自由地使用用户定义的字段扩展
PyCCallDef
结构,然后可以类似地访问这些字段。 - 在
PyCCallDef
结构是对象结构的一部分的情况下(例如,对于PyCFunctionObject为真),可以从PyCCallDef
指针中减去适当的偏移量以获取指向定义该PyCCallDef
的可调用对象的指针。
此PEP的早期版本定义了一个标志CCALL_FUNCARG
而不是CCALL_DEFARG
,它将可调用对象传递给被调用者。它有类似的用例,但对于绑定方法存在一些歧义: “可调用对象”应该是绑定方法对象还是方法包装的原始函数?通过传递PyCCallDef *
,这种歧义消失了,因为绑定方法使用包装函数的PyCCallDef *
。
替换 tp_print
我们将tp_print
重新用作tp_ccalloffset
,因为这使得外部项目更容易将C调用协议移植到早期Python版本。特别是,Cython项目已表示有兴趣这样做(请参见https://mail.python.org/pipermail/python-dev/2018-June/153927.html)。
替代建议
PEP 576是解决与本PEP相同问题的另一种方法。请参见https://mail.python.org/pipermail/python-dev/2018-July/154238.html,了解有关PEP 576和PEP 580之间区别的评论。
讨论
指向python-dev
邮件列表中讨论此PEP的主题的链接
- https://mail.python.org/pipermail/python-dev/2018-June/153938.html
- https://mail.python.org/pipermail/python-dev/2018-June/153984.html
- https://mail.python.org/pipermail/python-dev/2018-July/154238.html
- https://mail.python.org/pipermail/python-dev/2018-July/154470.html
- https://mail.python.org/pipermail/python-dev/2018-July/154571.html
- https://mail.python.org/pipermail/python-dev/2018-September/155166.html
- https://mail.python.org/pipermail/python-dev/2018-October/155403.html
- https://mail.python.org/pipermail/python-dev/2019-March/156853.html
- https://mail.python.org/pipermail/python-dev/2019-March/156879.html
参考实现
参考实现可以在https://github.com/jdemeyer/cpython/tree/pep580找到
有关使用C调用协议的示例,以下分支使用PEP 580实现了functools.lru_cache
:https://github.com/jdemeyer/cpython/tree/lru580
参考文献
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0580.rst
上次修改时间:2023-09-09 17:39:29 GMT