呼叫協定 (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"。這個函式不會呼叫失敗。
