调用协议
¶
CPython supports two different calling protocols:
tp_call
and vectorcall.
The
tp_call
Protocol
¶
Instances of classes that set
tp_call
are callable. The signature of the slot is:
PyObject *tp_call(PyObject *callable, PyObject *args, PyObject *kwargs);
A call is made using a tuple for the positional arguments and a dict for the keyword arguments, similarly to
callable(*args, **kwargs)
in Python code.
args
must be non-NULL (use an empty tuple if there are no arguments) but
kwargs
可以是
NULL
if there are no keyword arguments.
This convention is not only used by
tp_call
:
tp_new
and
tp_init
also pass arguments this way.
To call an object, use
PyObject_Call()
or another
call API
.
The Vectorcall Protocol
¶
Added in version 3.9.
The vectorcall protocol was introduced in
PEP 590
as an additional protocol for making calls more efficient.
As rule of thumb, CPython will prefer the vectorcall for internal calls if the callable supports it. However, this is not a hard rule. Additionally, some third-party extensions use
tp_call
directly (rather than using
PyObject_Call()
). Therefore, a class supporting vectorcall must also implement
tp_call
. Moreover, the callable must behave the same regardless of which protocol is used. The recommended way to achieve this is by setting
tp_call
to
PyVectorcall_Call()
. This bears repeating:
警告
A class supporting vectorcall
must
also implement
tp_call
with the same semantics.
3.12 版改变:
The
Py_TPFLAGS_HAVE_VECTORCALL
flag is now removed from a class when the class’s
__call__()
method is reassigned. (This internally sets
tp_call
only, and thus may make it behave differently than the vectorcall function.) In earlier Python versions, vectorcall should only be used with
immutable
or static types.
A class should not implement vectorcall if that would be slower than
tp_call
. For example, if the callee needs to convert the arguments to an args tuple and kwargs dict anyway, then there is no point in implementing vectorcall.
Classes can implement the vectorcall protocol by enabling the
Py_TPFLAGS_HAVE_VECTORCALL
flag and setting
tp_vectorcall_offset
to the offset inside the object structure where a
vectorcallfunc
appears. This is a pointer to a function with the following signature:
-
typedef
PyObject
*
(
*
vectorcallfunc
)
(
PyObject
*
callable
,
PyObject
*
const
*
args
,
size_t
nargsf
,
PyObject
*
kwnames
)
¶
-
属于
稳定 ABI (应用程序二进制接口)
since version 3.12.
-
PY_VECTORCALL_ARGUMENTS_OFFSET
¶
-
属于
稳定 ABI (应用程序二进制接口)
since version 3.12.
If this flag is set in a vectorcall
nargsf
argument, the callee is allowed to temporarily change
args[-1]
. In other words,
args
points to argument 1 (not 0) in the allocated vector. The callee must restore the value of
args[-1]
before returning.
For
PyObject_VectorcallMethod()
, this flag means instead that
args[0]
may be changed.
Whenever they can do so cheaply (without additional allocation), callers are encouraged to use
PY_VECTORCALL_ARGUMENTS_OFFSET
. Doing so will allow callables such as bound methods to make their onward calls (which include a prepended
self
argument) very efficiently.
Added in version 3.8.
To call an object that implements vectorcall, use a
call API
function as with any other callable.
PyObject_Vectorcall()
will usually be most efficient.
递归控制
¶
当使用
tp_call
, callees do not need to worry about
recursion
: CPython uses
Py_EnterRecursiveCall()
and
Py_LeaveRecursiveCall()
for calls made using
tp_call
.
For efficiency, this is not the case for calls done using vectorcall: the callee should use
Py_EnterRecursiveCall
and
Py_LeaveRecursiveCall
若需要。
Vectorcall Support API
¶
-
Py_ssize_t
PyVectorcall_NARGS
(
size_t
nargsf
)
¶
-
属于
稳定 ABI (应用程序二进制接口)
since version 3.12.
Given a vectorcall
nargsf
argument, return the actual number of arguments. Currently equivalent to:
(Py_ssize_t)(nargsf & ~PY_VECTORCALL_ARGUMENTS_OFFSET)