# 调用协议

CPython 支持两种不同的调用协议：tp_call 和 vectorcall。

## 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 版起.

  调用 callable 的 `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
--- | --- | --- | ---
`PyObject_Call()` | `PyObject *` | 元组 | dict/`NULL`
`PyObject_CallNoArgs()` | `PyObject *` | --- | ---
`PyObject_CallOneArg()` | `PyObject *` | 1 个对象 | ---
`PyObject_CallObject()` | `PyObject *` | 元组/`NULL` | ---
`PyObject_CallFunction()` | `PyObject *` | format | ---
`PyObject_CallMethod()` | 对象 + `char*` | format | ---
`PyObject_CallFunctionObjArgs()` | `PyObject *` | 可变参数 | ---
`PyObject_CallMethodObjArgs()` | 对象 + 名称 | 可变参数 | ---
`PyObject_CallMethodNoArgs()` | 对象 + 名称 | --- | ---
`PyObject_CallMethodOneArg()` | 对象 + 名称 | 1 个对象 | ---
`PyObject_Vectorcall()` | `PyObject *` | vectorcall | vectorcall
`PyObject_VectorcallDict()` | `PyObject *` | vectorcall | dict/`NULL`
`PyObject_VectorcallMethod()` | 参数 + 名称 | 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.8: 同 `_PyObject_Vectorcall`

  在 3.9 版本发生变更: 重命名为当前名称，不带开头的下划线。旧的暂定名称已设为 soft deprecated。

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.

## 调用支持 API

int PyCallable_Check(PyObject *o)
  属于 稳定 ABI.

  确定对象 o 是否可调用。如果对象是可调用的则返回 `1`，其他情况返回 `0`。这个函数不会调用失败。