# 呼叫協定 (Call Protocol)

CPython 支援兩種不同的呼叫協定：tp_call 和 vectorcall（向量呼叫）。

## tp_call 協定

設定 `tp_call` 的類別之實例都是可呼叫的。該擴充槽 (slot) 的簽章為：

    PyObject *tp_call(PyObject *callable, PyObject *args, PyObject *kwargs);

要達成一個呼叫會使用一個 tuple（元組）表示位置引數、一個 dict 表示關鍵字引數，類似於 Python 程式碼中的 `callable(*args, **kwargs)`。args 必須不為 NULL（如果沒有引數，會使用一個空 tuple），但如果沒有關鍵字引數，kwargs 可以是 NULL。

這個慣例不僅會被 tp_call 使用，`tp_new` 和 `tp_init` 也這樣傳遞引數。

使用 `PyObject_Call()` 或其他呼叫 API 來呼叫一個物件。

## Vectorcall 協定

在 3.9 版新加入.

Vectorcall 協定是在 PEP 590 被引入的，它是使函式呼叫更加有效率的附加協定。

經驗法則上，如果可呼叫物件有支援，CPython 於內部呼叫中會更傾向使用 vectorcall。然而，這並不是一個硬性規定。此外，有些第三方擴充套件會直接使用 tp_call（而不是使用 `PyObject_Call()`）。因此，一個支援 vectorcall 的類別也必須實作 `tp_call`。此外，無論使用哪種協定，可呼叫物件的行為都必須是相同的。要達成這個目的的推薦做法是將 `tp_call` 設定為 `PyVectorcall_Call()`。這值得一再提醒：

警告

一個支援 vectorcall 的類別必須也實作具有相同語義的 `tp_call`。

如果一個類別的 vectorcall 比 tp_call 慢，就不應該實作 vectorcall。例如，如果被呼叫者需要將引數轉換為 args tuple（引數元組）和 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 語言陣列 (array)，包含位置引數與後面

    關鍵字引數的值。如果沒有引數，這個值可以是 NULL。

- nargsf 是位置引數的數量加上可能會有的

    `PY_VECTORCALL_ARGUMENTS_OFFSET` 旗标。 要从 nargsf 获得位置参数的实际数量，请使用 `PyVectorcall_NARGS()`。

- kwnames 是一個包含所有關鍵字引數名稱的 tuple；

    換句話說，就是 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 参数）。

  在 3.8 版新加入.

要呼叫一個實作了 vectorcall 的物件，請就像其他可呼叫物件一樣使用呼叫 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` 函式以便將來需要擴充。

  在 3.8 版新加入.

vectorcallfunc PyVectorcall_Function( PyObject *op)

  如果 op 不支援 vectorcall 協定（因為型別不支援或特定實例不支援），就回傳 NULL。否則，回傳儲存在 op 中的 vectorcall 函式指標。這個函式不會引發例外。

  這大多在檢查 op 是否支援 vectorcall 時能派上用場，可以透過檢查 `PyVectorcall_Function(op) != NULL` 來達成。

  在 3.9 版新加入.

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

  呼叫 callable 的 `vectorcallfunc`，其位置引數和關鍵字引數分別以 tuple 和 dict 格式給定。

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

  在 3.8 版新加入.

## 物件呼叫 API

有多個函式可被用來呼叫 Python 物件。各個函式會將其引數轉換為被呼叫物件所支援的慣用形式 – 可以是 tp_call 或 vectorcall。為了儘可能減少轉換的進行，請選擇一個適合你所擁有資料格式的函式。

下表總結了可用的函式；請參閱各個說明文件以瞭解詳情。

函式 | callable | args | kwargs
--- | --- | --- | ---
`PyObject_Call()` | `PyObject *` | tuple | dict/`NULL`
`PyObject_CallNoArgs()` | `PyObject *` | --- | ---
`PyObject_CallOneArg()` | `PyObject *` | 一個物件 | ---
`PyObject_CallObject()` | `PyObject *` | tuple/`NULL` | ---
`PyObject_CallFunction()` | `PyObject *` | format | ---
`PyObject_CallMethod()` | 物件 + `char*` | format | ---
`PyObject_CallFunctionObjArgs()` | `PyObject *` | 可變引數 | ---
`PyObject_CallMethodObjArgs()` | 物件 + 名稱 | 可變引數 | ---
`PyObject_CallMethodNoArgs()` | 物件 + 名稱 | --- | ---
`PyObject_CallMethodOneArg()` | 物件 + 名稱 | 一個物件 | ---
`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，附帶由 tuple args 所給定的引數及由字典 kwargs 所給定的關鍵字引數。

  args 必須不為 NULL；如果不需要引數，請使用一個空 tuple。如果不需要關鍵字引數，則 kwargs 可以為 NULL。

  成功時回傳結果，或在失敗時引發一個例外並回傳 NULL。

  這等價於 Python 運算式 `callable(*args, **kwargs)`。

PyObject *PyObject_CallNoArgs( PyObject *callable)
  回傳值：新的參照。 属于 稳定 ABI 自 3.10 版起.

  呼叫一個可呼叫的 Python 物件 callable 並不附帶任何引數。這是不帶引數呼叫 Python 可呼叫物件的最有效方式。

  成功時回傳結果，或在失敗時引發一個例外並回傳 NULL。

  在 3.9 版新加入.

PyObject *PyObject_CallOneArg( PyObject *callable, PyObject *arg)
  回傳值：新的參照。

  呼叫一個可呼叫的 Python 物件 callable 並附帶正好一個位置引數 arg 而沒有關鍵字引數。

  成功時回傳結果，或在失敗時引發一個例外並回傳 NULL。

  在 3.9 版新加入.

PyObject *PyObject_CallObject( PyObject *callable, PyObject *args)
  回傳值：新的參照。 属于 稳定 ABI.

  呼叫一個可呼叫的 Python 物件 callable，附帶由 tuple args 所給定的引數。如果不需要傳入引數，則 args 可以為 NULL。

  成功時回傳結果，或在失敗時引發一個例外並回傳 NULL。

  這等價於 Python 運算式 `callable(*args)`。

PyObject *PyObject_CallFunction( PyObject *callable, const char *format, ...)
  回傳值：新的參照。 属于 稳定 ABI.

  呼叫一個可呼叫的 Python 物件 callable，附帶數量可變的 C 引數。這些 C 引數使用 `Py_BuildValue()` 風格的格式字串來描述。格式可以為 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 的 method 並附帶數量可變的 C 引數。這些 C 引數由 `Py_BuildValue()` 格式字串來描述，並應當生成一個 tuple。

  格式可以為 NULL，表示沒有提供任何引數。

  成功時回傳結果，或在失敗時引發一個例外並回傳 NULL。

  這等價於 Python 運算式 `obj.name(arg1, arg2, ...)`。

  注意，如果你只傳入 PyObject * 引數，則 `PyObject_CallMethodObjArgs()` 是另一個更快速的選擇。

  在 3.4 版的變更: name 和 format 的型別已從 `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 中的一個 method，其中 method 名稱由 name 中的 Python 字串物件給定。被呼叫時會附帶數量可變的 PyObject * 引數。這些引數是以位置在 NULL 後面、且數量可變的參數來提供。

  成功時回傳結果，或在失敗時引發一個例外並回傳 NULL。

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

  不附帶任何引數地呼叫 Python 物件 obj 中的一個 method，其中 method 名稱由 name 中的 Python 字串物件給定。

  成功時回傳結果，或在失敗時引發一個例外並回傳 NULL。

  在 3.9 版新加入.

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

  附帶一個位置引數 arg 地呼叫 Python 物件 obj 中的一個 method，其中 method 名稱由 name 中的 Python 字串物件給定。

  成功時回傳結果，或在失敗時引發一個例外並回傳 NULL。

  在 3.9 版新加入.

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

  呼叫一個可呼叫的 Python 物件 callable。附帶引數與 `vectorcallfunc` 的相同。如果 callable 支援 vectorcall，則它會直接呼叫存放在 callable 中的 vectorcall 函式。

  成功時回傳結果，或在失敗時引發一個例外並回傳 NULL。

  在 3.9 版新加入.

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

  附帶與在 vectorcall 協定中傳入的相同位置引數來呼叫 callable，但會加上以字典 kwdict 格式傳入的關鍵字引數。args 陣列將只包含位置引數。

  無論內部使用了哪一種協定，都會需要進行引數的轉換。因此，此函式應該只有在呼叫方已經擁有一個要作為關鍵字引數的字典、但沒有作為位置引數的 tuple 時才被使用。

  在 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。

  在 3.9 版新加入.

## 呼叫支援 API

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

  判定物件 o 是否為可呼叫的。如果物件是可呼叫物件則回傳 `1`，其他情況回傳 `0`。這個函式不會呼叫失敗。