# 呼叫協定 (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`。

在 3.12 版的變更: 當一個類別的 `__call__()` 方法被重新賦值時，`Py_TPFLAGS_HAVE_VECTORCALL` 旗標現在會被移除。（這會在內部設定 `tp_call`，因此可能會使它與 vectorcall 函式有不同的行為。）在較早的 Python 版本中，vectorcall 應該只與 `immutable` 或靜態型別一起使用。

如果一個類別的 vectorcall 比 tp_call 慢，就不應該實作 vectorcall。例如，如果被呼叫者需要將引數轉換為 args tuple（引數元組）和 kwargs dict（關鍵字引數字典），那麼實作 vectorcall 就沒有意義。

類別可以透過啟用 `Py_TPFLAGS_HAVE_VECTORCALL` 旗標並將 `tp_vectorcall_offset` 設定為物件結構中有出現 vectorcallfunc 的 offset 來實作 vectorcall 協定。這是一個指向具有以下簽章之函式的指標：

typedef PyObject *(*vectorcallfunc)(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames)
  為 穩定 ABI 的一部分 自 3.12 版本開始.

- callable 是指被呼叫的物件。

- args 是一個 C 語言陣列 (array)，包含位置引數與後面

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

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

    `PY_VECTORCALL_ARGUMENTS_OFFSET` 旗標。如果要從 nargsf 獲得實際的位置引數數量，請使用 `PyVectorcall_NARGS()`。

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

    換句話說，就是 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`。這樣做會讓如 bound method（繫結方法）之類的可呼叫函式非常有效地繼續向前呼叫（這類函式包含一個在首位的 self 引數）。

  在 3.8 版被加入.

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

  在 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)
  為 穩定 ABI 的一部分 自 3.12 版本開始.

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

  這是一個專門函式，其目的是被放入 `tp_call` 擴充槽或是用於 `tp_call` 的實作。它不會檢查 `Py_TPFLAGS_HAVE_VECTORCALL` 旗標並且它不會退回 (fall back) 使用 `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)
  為 穩定 ABI 的一部分 自 3.12 版本開始.

  呼叫一個可呼叫的 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)
  為 穩定 ABI 的一部分 自 3.12 版本開始.

  使用 vectorcall 呼叫慣例來呼叫一個 method。method 的名稱以 Python 字串 name 的格式給定。被呼叫 method 的物件為 args[0]，而 args 陣列從 args[1] 開始的部分則代表呼叫的引數。必須傳入至少一個位置引數。nargsf 為包括 args[0] 在內的位置引數的數量，如果 `args[0]` 的值可能被臨時改變則要再加上 `PY_VECTORCALL_ARGUMENTS_OFFSET`。關鍵字引數可以像在 `PyObject_Vectorcall()` 中一樣被傳入。

  如果物件具有 `Py_TPFLAGS_METHOD_DESCRIPTOR` 特性，這將以完整的 args 向量作為引數來呼叫 unbound method（未繫結方法）物件。

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

  在 3.9 版被加入.

## 呼叫支援 API

int PyCallable_Check(PyObject *o)
  為 穩定 ABI 的一部分.

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