调用协议 — Python 文档
呼叫协议
CPython 支持两种不同的调用协议:tp_call 和 vectorcall。
tp_call 协议
设置 tp_call 的类的实例是可调用的。 插槽的签名是:
PyObject *tp_call(PyObject *callable, PyObject *args, PyObject *kwargs);
调用是使用元组作为位置参数和字典作为关键字参数,类似于 Python 代码中的 callable(*args, **kwargs)
。 args 必须是非 NULL(如果没有参数,则使用空元组)但 kwargs 可能是 NULL 如果没有关键字参数。
这种约定不仅被 tp_call 使用:tp_new 和 tp_init 也以这种方式传递参数。
要调用对象,请使用 PyObject_Call() 或其他 调用 API。
矢量调用协议
3.9 版中的新功能。
vectorcall 协议是在 PEP 590 中引入的,作为提高调用效率的附加协议。
根据经验,如果可调用对象支持,CPython 将更喜欢使用 vectorcall 进行内部调用。 然而,这不是硬性规定。 此外,一些第三方扩展直接使用 tp_call(而不是使用 PyObject_Call())。 因此,支持向量调用的类还必须实现 tp_call。 此外,无论使用哪种协议,可调用对象的行为都必须相同。 实现此目的的推荐方法是将 tp_call 设置为 PyVectorcall_Call()。 这值得重复:
如果一个类比 tp_call 慢,则该类不应实现 vectorcall。 例如,如果被调用者无论如何都需要将参数转换为 args 元组和 kwargs dict,那么实现 vectorcall 就没有意义了。
类可以通过启用 Py_TPFLAGS_HAVE_VECTORCALL 标志并将 tp_vectorcall_offset 设置为出现 vectorcallfunc 的对象结构内的偏移量来实现 vectorcall 协议。 这是一个指向具有以下签名的函数的指针:
- typedef PyObject *(*vectorcallfunc)(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames)
- callable 是被调用的对象。
- *; args 是一个 C 数组,由位置参数后跟
- 关键字参数的值。 如果没有参数,这可以是 NULL。
- *; nargsf 是位置参数的数量加上可能的
PY_VECTORCALL_ARGUMENTS_OFFSET
标志。 要从 nargsf 获取位置参数的实际数量,请使用 PyVectorcall_NARGS()。
- *; kwnames 是一个包含关键字参数名称的元组;
- 换句话说,kwargs 字典的键。 这些名称必须是字符串(
str
的实例或子类)并且它们必须是唯一的。 如果没有关键字参数,则 kwnames 可以改为 NULL。
- 换句话说,kwargs 字典的键。 这些名称必须是字符串(
- PY_VECTORCALL_ARGUMENTS_OFFSET
如果在 vectorcall nargsf 参数中设置了此标志,则允许被调用者临时更改
args[-1]
。 换句话说,args 指向分配向量中的参数 1(不是 0)。 被调用者必须在返回前恢复args[-1]
的值。对于 PyObject_VectorcallMethod(),这个标志意味着
args[0]
可以改变。只要他们可以便宜地做到这一点(无需额外分配),就鼓励调用者使用
PY_VECTORCALL_ARGUMENTS_OFFSET
。 这样做将允许诸如绑定方法之类的可调用对象非常有效地进行它们的前向调用(包括一个前置的 self 参数)。
要调用实现 vectorcall 的对象,请使用 call API 函数与任何其他可调用对象一样。 PyObject_Vectorcall() 通常是最有效的。
笔记
在 CPython 3.8 中,vectorcall API 和相关函数暂时可用以下划线开头的名称:_PyObject_Vectorcall
、_Py_TPFLAGS_HAVE_VECTORCALL
、_PyObject_VectorcallMethod
、_PyVectorcall_Function
、_PyObject_CallOneArg
、_PyObject_CallMethodNoArgs
、_PyObject_CallMethodOneArg
。 此外,PyObject_VectorcallDict
可用作 _PyObject_FastCallDict
。 旧名称仍定义为新的非下划线名称的别名。
递归控制
使用tp_call时,被调用者无需担心递归:CPython使用Py_EnterRecursiveCall()和Py_LeaveRecursiveCall()进行调用tp_call。
为了提高效率,使用 vectorcall 完成的调用不是这种情况:如果需要,被调用者应该使用 Py_EnterRecursiveCall 和 Py_LeaveRecursiveCall。
矢量调用支持 API
- Py_ssize_t PyVectorcall_NARGS(size_t nargsf)
给定一个 vectorcall nargsf 参数,返回参数的实际数量。 目前相当于:
(Py_ssize_t)(nargsf & ~PY_VECTORCALL_ARGUMENTS_OFFSET)
然而,函数
PyVectorcall_NARGS
应该用于允许未来的扩展。此函数不是 受限 API 的一部分。
3.8 版中的新功能。
- vectorcallfunc PyVectorcall_Function(PyObject *op)
如果op不支持vectorcall协议(要么类型不支持,要么具体实例不支持),返回NULL。 否则,返回存储在 op 中的 vectorcall 函数指针。 此函数从不引发异常。
这对于检查 op 是否支持向量调用非常有用,这可以通过检查
PyVectorcall_Function(op) != NULL
来完成。此函数不是 受限 API 的一部分。
3.8 版中的新功能。
- PyObject *PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *dict)
分别使用元组和字典中给出的位置和关键字参数调用 callable 的 vectorcallfunc。
这是一个专门的函数,旨在放置在 tp_call 插槽中或用于
tp_call
的实现中。 它不会检查 Py_TPFLAGS_HAVE_VECTORCALL 标志,也不会回退到tp_call
。此函数不是 受限 API 的一部分。
3.8 版中的新功能。
对象调用API
有多种函数可用于调用 Python 对象。 每个都将其参数转换为被调用对象支持的约定——tp_call 或 vectorcall。 为了尽可能少地进行转换,请选择最适合您可用数据格式的转换。
下表总结了可用的功能; 有关详细信息,请参阅个别文档。
功能 | 可调用的 | 参数 | kwargs |
---|---|---|---|
PyObject_Call()
|
PyObject *
|
元组 | 字典/NULL
|
PyObject_CallNoArgs()
|
PyObject *
|
— | — |
PyObject_CallOneArg()
|
PyObject *
|
1 个对象 | — |
PyObject_CallObject()
|
PyObject *
|
元组/NULL
|
— |
PyObject_CallFunction()
|
PyObject *
|
格式 | — |
PyObject_CallMethod()
|
物镜 + char*
|
格式 | — |
PyObject_CallFunctionObjArgs()
|
PyObject *
|
可变参数 | — |
PyObject_CallMethodObjArgs()
|
对象 + 名称 | 可变参数 | — |
PyObject_CallMethodNoArgs()
|
对象 + 名称 | — | — |
PyObject_CallMethodOneArg()
|
对象 + 名称 | 1 个对象 | — |
PyObject_Vectorcall()
|
PyObject *
|
向量调用 | 向量调用 |
PyObject_VectorcallDict()
|
PyObject *
|
向量调用 | 字典/NULL
|
PyObject_VectorcallMethod()
|
参数 + 名称 | 向量调用 | 向量调用 |
- PyObject *PyObject_Call(PyObject *callable, PyObject *args, PyObject *kwargs)
调用一个可调用的 Python 对象 callable,参数由元组 args 给出,命名参数由字典 kwargs 给出。
args 不能是 NULL; 如果不需要参数,请使用空元组。 如果不需要命名参数,kwargs 可以是 NULL。
成功时返回调用结果,或引发异常并在失败时返回 NULL。
这相当于 Python 表达式:
callable(*args, **kwargs)
。
- PyObject *PyObject_CallNoArgs(PyObject *callable)
不带任何参数调用可调用的 Python 对象 callable。 这是在没有任何参数的情况下调用可调用 Python 对象的最有效方法。
成功时返回调用结果,或引发异常并在失败时返回 NULL。
3.9 版中的新功能。
- PyObject *PyObject_CallOneArg(PyObject *callable, PyObject *arg)
使用恰好 1 个位置参数 arg 并且没有关键字参数调用可调用 Python 对象 callable。
成功时返回调用结果,或引发异常并在失败时返回 NULL。
此函数不是 受限 API 的一部分。
3.9 版中的新功能。
- PyObject *PyObject_CallObject(PyObject *callable, PyObject *args)
调用可调用 Python 对象 callable,参数由元组 args 给出。 如果不需要参数,则 args 可以是 NULL。
成功时返回调用结果,或引发异常并在失败时返回 NULL。
这相当于 Python 表达式:
callable(*args)
。
- PyObject *PyObject_CallFunction(PyObject *callable, const char *format, ...)
使用可变数量的 C 参数调用可调用 Python 对象 callable。 C 参数使用 Py_BuildValue() 样式格式字符串进行描述。 格式可以是NULL,表示不提供参数。
成功时返回调用结果,或引发异常并在失败时返回 NULL。
这相当于 Python 表达式:
callable(*args)
。请注意,如果您只传递 PyObject* 参数,则 PyObject_CallFunctionObjArgs() 是一个更快的选择。
3.4 版本变更:格式的类型由
char *
变更。
- PyObject *PyObject_CallMethod(PyObject *obj, const char *name, const char *format, ...)
使用可变数量的 C 参数调用对象 obj 的名为 name 的方法。 C 参数由应生成元组的 Py_BuildValue() 格式字符串描述。
格式可以是NULL,表示不提供参数。
成功时返回调用结果,或引发异常并在失败时返回 NULL。
这相当于 Python 表达式:
obj.name(arg1, arg2, ...)
。请注意,如果您只传递 PyObject* 参数,则 PyObject_CallMethodObjArgs() 是一个更快的选择。
3.4 版本变更: name 和 format 的类型由
char *
变更。
- PyObject *PyObject_CallFunctionObjArgs(PyObject *callable, ...)
使用可变数量的 PyObject* 参数调用可调用 Python 对象 callable。 参数以可变数量的参数形式提供,后跟 NULL。
成功时返回调用结果,或引发异常并在失败时返回 NULL。
这相当于 Python 表达式:
callable(arg1, arg2, ...)
。
- PyObject *PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)
调用 Python 对象 obj 的方法,其中方法的名称在 name 中作为 Python 字符串对象给出。 它使用可变数量的 PyObject* 参数调用。 参数以可变数量的参数形式提供,后跟 NULL。
成功时返回调用结果,或引发异常并在失败时返回 NULL。
- PyObject *PyObject_CallMethodNoArgs(PyObject *obj, PyObject *name)
不带参数调用 Python 对象 obj 的方法,其中方法的名称在 name 中作为 Python 字符串对象给出。
成功时返回调用结果,或引发异常并在失败时返回 NULL。
此函数不是 受限 API 的一部分。
3.9 版中的新功能。
- PyObject *PyObject_CallMethodOneArg(PyObject *obj, PyObject *name, PyObject *arg)
使用单个位置参数 arg 调用 Python 对象 obj 的方法,其中该方法的名称在 name 中作为 Python 字符串对象给出。
成功时返回调用结果,或引发异常并在失败时返回 NULL。
此函数不是 受限 API 的一部分。
3.9 版中的新功能。
- PyObject *PyObject_Vectorcall(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames)
调用一个可调用的 Python 对象 callable。 参数与 vectorcallfunc 相同。 如果callable支持vectorcall,则直接调用存储在callable中的vectorcall函数。
成功时返回调用结果,或引发异常并在失败时返回 NULL。
此函数不是 受限 API 的一部分。
3.9 版中的新功能。
- PyObject *PyObject_VectorcallDict(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwdict)
使用完全按照 vectorcall 协议传递的位置参数调用 callable,但使用作为字典 kwdict 传递的关键字参数。 args 数组只包含位置参数。
无论内部使用哪种协议,都需要进行参数转换。 因此,仅当调用者已经准备好用于关键字参数的字典而不是位置参数的元组时,才应使用此函数。
此函数不是 受限 API 的一部分。
3.9 版中的新功能。
- PyObject *PyObject_VectorcallMethod(PyObject *name, PyObject *const *args, size_t nargsf, PyObject *kwnames)
使用 vectorcall 调用约定调用方法。 该方法的名称以 Python 字符串 name 的形式给出。 其方法被调用的对象是args[0],从args[1]开始的args数组代表调用的参数。 必须至少有一个位置参数。 nargsf 是位置参数的数量,包括 args[0],如果
args[0]
的值可能会临时更改,则加上PY_VECTORCALL_ARGUMENTS_OFFSET
。 关键字参数可以像在 PyObject_Vectorcall() 中一样传递。如果对象具有 Py_TPFLAGS_METHOD_DESCRIPTOR 功能,这将调用具有完整 args 向量作为参数的未绑定方法对象。
成功时返回调用结果,或引发异常并在失败时返回 NULL。
此函数不是 受限 API 的一部分。
3.9 版中的新功能。