# 调用协议

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 协议

3.9 新版功能.

vectorcall 协议是在 PEP 590 被引入的，它是使调用函数更加有效的附加协议。

作为经验法则，如果可调用程序支持 vectorcall，CPython 会更倾向于内联调用。 然而，这并不是一个硬性规定。 此外，一些第三方扩展直接使用 tp_call (而不是使用 `PyObject_Call()`)。 因此，一个支持 vectorcall 的类也必须实现 `tp_call`。 此外，无论使用哪种协议，可调对象的行为都必须是相同的。 推荐的方法是将 `tp_call` 设置为 `PyVectorcall_Call()`。值得一提的是:

警告

一个支持 Vectorcall 的类 必须 也实现具有相同语义的 `tp_call`。

如果一个类的vectorcall比*tp_call*慢，就不应该实现vectorcall。例如，如果被调用者需要将参数转换为args 元组和kwargs dict，那么实现vectorcall就没有意义。

类可以通过启用 `Py_TPFLAGS_HAVE_VECTORCALL` 标志并将 `tp_vectorcall_offset` 设置为对象结构中的 vectorcallfunc 的 offset 来实现 vectorcall 协议。这是一个指向具有以下签名的函数的指针:

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 代替。

`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 对于使用 tp_call 进行的调用会使用 `Py_EnterRecursiveCall()` 和 `Py_LeaveRecursiveCall()`。

为保证效率，这不适用于使用 vectorcall 的调用：被调用方在需要时应当使用 Py_EnterRecursiveCall 和 Py_LeaveRecursiveCall。

### Vectorcall 支持 API

Py_ssize_t `PyVectorcall_NARGS`(size_t nargsf)

  给定一个 vectorcall nargsf 实参，返回参数的实际数量。 目前等同于:

      (Py_ssize_t)(nargsf & ~PY_VECTORCALL_ARGUMENTS_OFFSET)

  然而，应使用 `PyVectorcall_NARGS` 函数以便将来扩展。

  这个函数不是 limited API 的一部分。

  3.8 新版功能.

vectorcallfunc `PyVectorcall_Function`(PyObject *op)

  如果*op*不支持vectorcall协议（要么是因为类型不支持，要么是因为具体实例不支持），返回*NULL*。否则，返回存储在*op*中的vectorcall函数指针。这个函数从不触发异常。

  这在检查 op 是否支持 vectorcall 时最有用处，可以通过检查 `PyVectorcall_Function(op) != NULL` 来实现。

  这个函数不是 limited API 的一部分。

  3.8 新版功能.

PyObject* `PyVectorcall_Call`(PyObject *callable, PyObject *tuple, PyObject *dict)

  调用*可调对象*的 `vectorcallfunc`，其位置参数和关键字参数分别以元组和dict形式给出。

  这是一个专门函数，其目的是被放入 `tp_call` 槽位或是用于 `tp_call` 的实现。 它不会检查 `Py_TPFLAGS_HAVE_VECTORCALL` 旗标并且它不会回退到 `tp_call`。

  这个函数不是 limited API 的一部分。

  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)
  Return value: New reference.

  调用一个可调用的 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)

  调用一个可调用的 Python 对象 callable 并附带恰好 1 个位置参数 arg 而没有关键字参数。

  成功时返回结果，在失败时抛出一个异常并返回 NULL。

  这个函数不是 limited API 的一部分。

  3.9 新版功能.

PyObject* `PyObject_CallObject`(PyObject *callable, PyObject *args)
  Return value: New reference.

  调用一个可调用的 Python 对象 callable，附带由元组 args 所给出的参数。 如果不想要传入参数，则 args 可以为 NULL。

  成功时返回结果，在失败时抛出一个异常并返回 NULL。

  这等价于 Python 表达式 `callable(*args)`。

PyObject* `PyObject_CallFunction`(PyObject *callable, const char *format, ...)
  Return value: New reference.

  调用一个可调用的 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, ...)
  Return value: New reference.

  调用 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, ...)
  Return value: New reference.

  调用一个可调用的 Python 对象 callable，附带可变数量的 `PyObject *` 参数。 这些参数以可变数量的形参的形式提供并以 NULL 结尾。

  成功时返回结果，在失败时抛出一个异常并返回 NULL。

  这和Python表达式 `callable(arg1, arg2, ...)` 是一样的。

PyObject* `PyObject_CallMethodObjArgs`(PyObject *obj, PyObject *name, ...)
  Return value: New reference.

  调用 Python 对象 obj 中的一个方法，其中方法名称由 name 中的 Python 字符串对象给出。 它将附带可变数量的 `PyObject *` 参数被调用。 这些参数以可变数量的形参的形式提供并以 NULL 结尾。

  成功时返回结果，在失败时抛出一个异常并返回 NULL。

PyObject* `PyObject_CallMethodNoArgs`(PyObject *obj, PyObject *name)

  调用 Python 对象 obj 中的一个方法并不附带任何参数，其中方法名称由 name 中的 Python 字符串对象给出。

  成功时返回结果，在失败时抛出一个异常并返回 NULL。

  这个函数不是 limited API 的一部分。

  3.9 新版功能.

PyObject* `PyObject_CallMethodOneArg`(PyObject *obj, PyObject *name, PyObject *arg)

  调用 Python 对象 obj 中的一个方法并附带单个位置参数 arg，其中方法名称由 name 中的 Python 字符串对象给出。

  成功时返回结果，在失败时抛出一个异常并返回 NULL。

  这个函数不是 limited API 的一部分。

  3.9 新版功能.

PyObject* `PyObject_Vectorcall`(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames)

  调用一个可调用的 Python 对象 callable。 附带的参数与 `vectorcallfunc` 相同。 如果 callable 支持 vectorcall，则它会直接调用存放在 callable 中的 vectorcall 函数。

  成功时返回结果，在失败时抛出一个异常并返回 NULL。

  这个函数不是 limited API 的一部分。

  3.9 新版功能.

PyObject* `PyObject_VectorcallDict`(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwdict)

  调用 callable 并附带与在 vectorcall 协议中传入的完全相同的位置参数，但会加上以字典 kwdict 形式传入的关键字参数。 args 数组将只包含位置参数。

  无论在内部使用哪种协议，都需要进行参数的转换。 因此，此函数应当仅在调用方已经拥有作为关键字参数的字典，但没有作为位置参数的元组时才被使用。

  这个函数不是 limited API 的一部分。

  3.9 新版功能.

PyObject* `PyObject_VectorcallMethod`(PyObject *name, PyObject *const *args, size_t nargsf, PyObject *kwnames)

  使用 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。

  这个函数不是 limited API 的一部分。

  3.9 新版功能.

## 调用支持 API

int `PyCallable_Check`(PyObject *o)

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