调用协议¶
CPython 支持两种不同的调用协议:tp_call 和矢量调用。
tp_call 协议¶
设置 tp_call
的类的实例都是可调用的。 槽位的签名为:
PyObject *tp_call(PyObject *callable, PyObject *args, PyObject *kwargs);
一个调用是用一个元组表示位置参数,用一个dict表示关键字参数,类似于Python代码中的 callable(*args, **kwargs)
。args*必须是非空的(如果没有参数,会使用一个空元组),但如果没有关键字参数,*kwargs*可以是*NULL。
这个约定不仅被*tp_call*使用: tp_new
和 tp_init
也这样传递参数。
要调用一个对象,请使用 PyObject_Call()
或者其他的 调用 API。
Vectorcall 协议¶
Added in version 3.9.
vectorcall 协议是在 PEP 590 被引入的,它是使调用函数更加有效的附加协议。
作为经验法则,如果可调用程序支持 vectorcall,CPython 会更倾向于内联调用。 然而,这并不是一个硬性规定。 此外,一些第三方扩展直接使用 tp_call (而不是使用 PyObject_Call()
)。 因此,一个支持 vectorcall 的类也必须实现 tp_call
。 此外,无论使用哪种协议,可调对象的行为都必须是相同的。 推荐的方法是将 tp_call
设置为 PyVectorcall_Call()
。值得一提的是:
警告
一个支持 Vectorcall 的类 必须 也实现具有相同语义的 tp_call
。
在 3.12 版本发生变更: 现在 Py_TPFLAGS_HAVE_VECTORCALL
旗标在类的 __call__()
方法被重新赋值时将会从类中移除。 (这将仅在内部设置 tp_call
,因此可能使其行为不同于 vectorcall 函数。) 在更早的 Python 版本中,vectorcall 应当仅被用于 不可变对象
或静态类型。
如果一个类的vectorcall比*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)¶
- 属于 稳定 ABI 自 3.12 版起.
callable 是指被调用的对象。
- args 是一个C语言数组,由位置参数和后面的
关键字参数的值。如果没有参数,这个值可以是 NULL 。
- nargsf 是位置参数的数量加上可能的
PY_VECTORCALL_ARGUMENTS_OFFSET
旗标。 要从 nargsf 获得位置参数的实际数量,请使用PyVectorcall_NARGS()
。
- kwnames 是一包含所有关键字名称的元组。
换句话说,就是 kwargs 字典的键。 这些名字必须是字符串 (
str
或其子类的实例),并且它们必须是唯一的。 如果没有关键字参数,那么 kwnames 可以用 NULL 代替。
-
PY_VECTORCALL_ARGUMENTS_OFFSET¶
- 属于 稳定 ABI 自 3.12 版起.
如果在 vectorcall 的 nargsf 参数中设置了此标志,则允许被调用者临时更改
args[-1]
的值。换句话说, args 指向分配向量中的参数 1(不是 0 )。被调用方必须在返回之前还原args[-1]
的值。对于
PyObject_VectorcallMethod()
,这个标志的改变意味着args[0]
可能改变了。只要调用方能以低代价(不额外分配内存)这样做,就推荐使用
PY_VECTORCALL_ARGUMENTS_OFFSET
。 这样做将允许诸如绑定方法之类的可调用对象非常高效地执行前向调用(这种调用将包括一个加在开头的 self 参数)。Added in version 3.8.
要调用一个实现了 vectorcall 的对象,请使用某个 call API 函数,就像其他可调对象一样。 PyObject_Vectorcall()
通常是最有效的。
递归控制¶
在使用 tp_call 时,被调用者不必担心 递归: CPython 对于使用 tp_call 进行的调用会使用 Py_EnterRecursiveCall()
和 Py_LeaveRecursiveCall()
。
为保证效率,这不适用于使用 vectorcall 的调用:被调用方在需要时应当使用 Py_EnterRecursiveCall 和 Py_LeaveRecursiveCall。
Vectorcall 支持 API¶
-
Py_ssize_t PyVectorcall_NARGS(size_t nargsf)¶
- 属于 稳定 ABI 自 3.12 版起.
给定一个 vectorcall nargsf 实参,返回参数的实际数量。 目前等同于:
(Py_ssize_t)(nargsf & ~PY_VECTORCALL_ARGUMENTS_OFFSET)
然而,应使用
PyVectorcall_NARGS
函数以便将来扩展。Added in version 3.8.
-
vectorcallfunc PyVectorcall_Function(PyObject *op)¶
如果*op*不支持vectorcall协议(要么是因为类型不支持,要么是因为具体实例不支持),返回*NULL*。否则,返回存储在*op*中的vectorcall函数指针。这个函数从不触发异常。
这在检查 op 是否支持 vectorcall 时最有用处,可以通过检查
PyVectorcall_Function(op) != NULL
来实现。Added in version 3.9.
-
PyObject *PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *dict)¶
- 属于 稳定 ABI 自 3.12 版起.
调用*可调对象*的
vectorcallfunc
,其位置参数和关键字参数分别以元组和dict形式给出。这是一个专用函数,用于放入
tp_call
槽位或是用于tp_call
的实现。 它不会检查Py_TPFLAGS_HAVE_VECTORCALL
旗标并且它也不会回退到tp_call
。Added in version 3.8.
调用对象的 API¶
有多个函数可被用来调用 Python 对象。 各个函数会将其参数转换为被调用对象所支持的惯例 – 可以是 tp_call 或 vectorcall。 为了尽可能少地进行转换,请选择一个适合你所拥有的数据格式的函数。
下表总结了可用的功能; 请参阅各个文档以了解详细信息。
函数 |
callable -- 可调用对象 |
args |
kwargs |
---|---|---|---|
|
元组 |
dict/ |
|
|
--- |
--- |
|
|
1个对象 |
--- |
|
|
元组/ |
--- |
|
|
format |
--- |
|
对象 + |
format |
--- |
|
|
可变参数 |
--- |
|
对象 + 名称 |
可变参数 |
--- |
|
对象 + 名称 |
--- |
--- |
|
对象 + 名称 |
1个对象 |
--- |
|
|
vectorcall |
vectorcall |
|
|
vectorcall |
dict/ |
|
参数 + 名称 |
vectorcall |
vectorcall |
-
PyObject *PyObject_Call(PyObject *callable, PyObject *args, PyObject *kwargs)¶
- 返回值:新的引用。 属于 稳定 ABI.
调用一个可调用的 Python 对象 callable,附带由元组 args 所给出的参数,以及由字典 kwargs 所给出的关键字参数。
args 必须不为 NULL;如果不想要参数请使用一个空元组。 如果不想要关键字参数,则 kwargs 可以为 NULL。
成功时返回结果,在失败时抛出一个异常并返回 NULL。
这等价于 Python 表达式
callable(*args, **kwargs)
。
-
PyObject *PyObject_CallNoArgs(PyObject *callable)¶
- 返回值:新的引用。 属于 稳定 ABI 自 3.10 版起.
调用一个可调用的 Python 对象 callable 并不附带任何参数。 这是不带参数调用 Python 可调用对象的最有效方式。
成功时返回结果,在失败时抛出一个异常并返回 NULL。
Added in version 3.9.
-
PyObject *PyObject_CallOneArg(PyObject *callable, PyObject *arg)¶
- 返回值:新的引用。
调用一个可调用的 Python 对象 callable 并附带恰好 1 个位置参数 arg 而没有关键字参数。
成功时返回结果,在失败时抛出一个异常并返回 NULL。
Added in version 3.9.
-
PyObject *PyObject_CallObject(PyObject *callable, PyObject *args)¶
- 返回值:新的引用。 属于 稳定 ABI.
调用一个可调用的 Python 对象 callable,附带由元组 args 所给出的参数。 如果不想要传入参数,则 args 可以为 NULL。
成功时返回结果,在失败时抛出一个异常并返回 NULL。
这等价于 Python 表达式
callable(*args)
。
-
PyObject *PyObject_CallFunction(PyObject *callable, const char *format, ...)¶
- 返回值:新的引用。 属于 稳定 ABI.
调用一个可调用的 Python 对象 callable,附带可变数量的 C 参数。 这些 C 参数使用
Py_BuildValue()
风格的格式字符串来描述。 format 可以为 NULL,表示没有提供任何参数。成功时返回结果,在失败时抛出一个异常并返回 NULL。
这等价于 Python 表达式
callable(*args)
。请注意如果你只传入 PyObject* 参数,则
PyObject_CallFunctionObjArgs()
是更快速的选择。在 3.4 版本发生变更: 这个 format 类型已从
char *
更改。
-
PyObject *PyObject_CallMethod(PyObject *obj, const char *name, const char *format, ...)¶
- 返回值:新的引用。 属于 稳定 ABI.
调用 obj 对象中名为 name 的方法并附带可变数量的 C 参数。 这些 C 参数由
Py_BuildValue()
格式字符串来描述并应当生成一个元组。格式可以为 NULL ,表示未提供任何参数。
成功时返回结果,在失败时抛出一个异常并返回 NULL。
这和Python表达式
obj.name(arg1, arg2, ...)
是一样的。请注意如果你只传入 PyObject* 参数,则
PyObject_CallMethodObjArgs()
是更快速的选择。在 3.4 版本发生变更: The types of name and format were changed from
char *
.
-
PyObject *PyObject_CallFunctionObjArgs(PyObject *callable, ...)¶
- 返回值:新的引用。 属于 稳定 ABI.
调用一个 Python 可调用对象 callable,附带可变数量的 PyObject* 参数。 这些参数是以 NULL 之后可变数量的形参的形式提供的。
成功时返回结果,在失败时抛出一个异常并返回 NULL。
这和Python表达式
callable(arg1, arg2, ...)
是一样的。
-
PyObject *PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)¶
- 返回值:新的引用。 属于 稳定 ABI.
调用 Python 对象 obj 中的一个访问,其中方法名称由 name 中的 Python 字符串对象给出。 它将附带可变数量的 PyObject* 参数被调用。 这些参数是以 NULL 之后可变数量的形参的形式提供的。
成功时返回结果,在失败时抛出一个异常并返回 NULL。
-
PyObject *PyObject_CallMethodNoArgs(PyObject *obj, PyObject *name)¶
调用 Python 对象 obj 中的一个方法并不附带任何参数,其中方法名称由 name 中的 Python 字符串对象给出。
成功时返回结果,在失败时抛出一个异常并返回 NULL。
Added in version 3.9.
-
PyObject *PyObject_CallMethodOneArg(PyObject *obj, PyObject *name, PyObject *arg)¶
调用 Python 对象 obj 中的一个方法并附带单个位置参数 arg,其中方法名称由 name 中的 Python 字符串对象给出。
成功时返回结果,在失败时抛出一个异常并返回 NULL。
Added in version 3.9.
-
PyObject *PyObject_Vectorcall(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames)¶
- 属于 稳定 ABI 自 3.12 版起.
调用一个可调用的 Python 对象 callable。 附带的参数与
vectorcallfunc
相同。 如果 callable 支持 vectorcall,则它会直接调用存放在 callable 中的 vectorcall 函数。成功时返回结果,在失败时抛出一个异常并返回 NULL。
Added in version 3.9.
-
PyObject *PyObject_VectorcallDict(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwdict)¶
调用 callable 并附带与在 vectorcall 协议中传入的完全相同的位置参数,但会加上以字典 kwdict 形式传入的关键字参数。 args 数组将只包含位置参数。
无论在内部使用哪种协议,都需要进行参数的转换。 因此,此函数应当仅在调用方已经拥有作为关键字参数的字典,但没有作为位置参数的元组时才被使用。
Added in version 3.9.
-
PyObject *PyObject_VectorcallMethod(PyObject *name, PyObject *const *args, size_t nargsf, PyObject *kwnames)¶
- 属于 稳定 ABI 自 3.12 版起.
使用 vectorcall 调用惯例来调用一个方法。 方法的名称以 Python 字符串 name 的形式给出。 调用方法的对象为 args[0],而 args 数组从 args[1] 开始的部分则代表调用的参数。 必须传入至少一个位置参数。 nargsf 为包括 args[0] 在内的位置参数的数量,如果
args[0]
的值可能被临时改变则还要加上PY_VECTORCALL_ARGUMENTS_OFFSET
。 关键字参数可以像在PyObject_Vectorcall()
中那样传入。如果对象具有
Py_TPFLAGS_METHOD_DESCRIPTOR
特性,此函数将调用未绑定的方法对象并传入完整的 args vector 作为参数。成功时返回结果,在失败时抛出一个异常并返回 NULL。
Added in version 3.9.