呼叫協議

CPython 支援兩種不同的呼叫協議:`tp_call` 和 vectorcall。

`tp_call` 協議

設定了 tp_call 的類例項是可呼叫的。該槽的簽名是

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

呼叫使用元組作為位置引數,使用字典作為關鍵字引數,類似於 Python 程式碼中的 callable(*args, **kwargs)。`args` 必須非 NULL(如果沒有引數,則使用空元組),但如果沒有任何關鍵字引數,`kwargs` 可以是 `NULL`。

此約定不僅由 `tp_call` 使用:tp_newtp_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

3.12 版本中的變化: 當類的 __call__() 方法被重新賦值時,Py_TPFLAGS_HAVE_VECTORCALL 標誌將從類中移除。(這隻會內部設定 tp_call,因此可能會使其行為與 vectorcall 函式不同。)在早期 Python 版本中,vectorcall 應該只與 不可變 或靜態型別一起使用。

如果 vectorcall 會比 `tp_call` 慢,類就不應該實現 vectorcall。例如,如果被呼叫者無論如何都需要將引數轉換為 args 元組和 kwargs 字典,那麼就沒有必要實現 vectorcall。

類可以透過啟用 Py_TPFLAGS_HAVE_VECTORCALL 標誌並將 tp_vectorcall_offset 設定為物件結構中出現 `vectorcallfunc` 的偏移量來支援 vectorcall 協議。這是一個指向具有以下簽名的函式的指標

typedef PyObject *(*vectorcallfunc)(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames)
自 3.12 版本起成為 穩定 ABI 的一部分。
  • `callable` 是被呼叫的物件。

  • `args` 是一個 C 陣列,由位置引數後跟

    關鍵字引數的值組成。如果沒有引數,這可以是 `NULL`。

  • `nargsf` 是位置引數的數量加上可能的

    PY_VECTORCALL_ARGUMENTS_OFFSET 標誌。要從 `nargsf` 獲取實際的位置引數數量,請使用 PyVectorcall_NARGS()

  • `kwnames` 是一個包含關鍵字引數名稱的元組;

    換句話說,就是 kwargs 字典的鍵。這些名稱必須是字串(`str` 或其子類的例項),並且必須是唯一的。如果沒有關鍵字引數,那麼 `kwnames` 可以是 `NULL`。

PY_VECTORCALL_ARGUMENTS_OFFSET
自 3.12 版本起成為 穩定 ABI 的一部分。

如果在 vectorcall 的 `nargsf` 引數中設定了此標誌,則允許被呼叫者臨時更改 args[-1]。換句話說,`args` 指向已分配向量中的引數 1(而不是 0)。被呼叫者在返回之前必須恢復 args[-1] 的值。

對於 PyObject_VectorcallMethod(),此標誌表示 args[0] 可能會被更改。

只要能夠廉價地(無需額外分配)做到,鼓勵呼叫者使用 PY_VECTORCALL_ARGUMENTS_OFFSET。這樣做將允許可呼叫物件(例如繫結方法)非常高效地進行其後續呼叫(其中包括一個前置的 `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)
自 3.12 版本起成為 穩定 ABI 的一部分。

給定 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)
自 3.12 版本起成為 穩定 ABI 的一部分。

呼叫 `callable` 的 vectorcallfunc,其中位置引數和關鍵字引數分別以元組和字典形式給出。

這是一個專用函式,旨在放置在 tp_call 槽中或用於 `tp_call` 的實現。它不檢查 Py_TPFLAGS_HAVE_VECTORCALL 標誌,也不回退到 `tp_call`。

在 3.8 版本加入。

物件呼叫 API

有多種函式可用於呼叫 Python 物件。每個函式都會將其引數轉換為被呼叫物件支援的約定——無論是 `tp_call` 還是 vectorcall。為了儘量減少轉換,請選擇最適合您可用資料格式的函式。

下表總結了可用函式;詳情請參閱各自的文件。

函式

可呼叫

args

關鍵字引數

PyObject_Call()

PyObject *

元組

字典/NULL

PyObject_CallNoArgs()

PyObject *

PyObject_CallOneArg()

PyObject *

1 個物件

PyObject_CallObject()

PyObject *

元組/NULL

PyObject_CallFunction()

PyObject *

format

PyObject_CallMethod()

obj + char*

format

PyObject_CallFunctionObjArgs()

PyObject *

可變引數

PyObject_CallMethodObjArgs()

obj + 名稱

可變引數

PyObject_CallMethodNoArgs()

obj + 名稱

PyObject_CallMethodOneArg()

obj + 名稱

1 個物件

PyObject_Vectorcall()

PyObject *

vectorcall

vectorcall

PyObject_VectorcallDict()

PyObject *

vectorcall

字典/NULL

PyObject_VectorcallMethod()

arg + 名稱

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)
返回值:新引用。 自 3.10 版本起成為 穩定 ABI 的一部分。

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

成功時返回呼叫的結果,失敗時引發異常並返回 `NULL`。

在 3.9 版本中新增。

PyObject *PyObject_CallOneArg(PyObject *callable, PyObject *arg)
返回值:新引用。

呼叫可呼叫 Python 物件 `callable`,帶恰好 1 個位置引數 `arg`,不帶關鍵字引數。

成功時返回呼叫的結果,失敗時引發異常並返回 `NULL`。

在 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() 風格的格式字串描述。格式可以為 `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 版本中的變化: `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` 的方法,其中方法的名稱以 Python 字串物件 `name` 給出。它帶可變數量的 PyObject* 引數進行呼叫。引數作為可變數量的引數後跟 `NULL` 提供。

成功時返回呼叫的結果,失敗時引發異常並返回 `NULL`。

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

不帶引數呼叫 Python 物件 `obj` 的方法,其中方法的名稱以 Python 字串物件 `name` 給出。

成功時返回呼叫的結果,失敗時引發異常並返回 `NULL`。

在 3.9 版本中新增。

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

呼叫 Python 物件 `obj` 的方法,帶單個位置引數 `arg`,其中方法的名稱以 Python 字串物件 `name` 給出。

成功時返回呼叫的結果,失敗時引發異常並返回 `NULL`。

在 3.9 版本中新增。

PyObject *PyObject_Vectorcall(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames)
自 3.12 版本起成為 穩定 ABI 的一部分。

呼叫可呼叫 Python 物件 `callable`。引數與 vectorcallfunc 相同。如果 `callable` 支援 vectorcall,則直接呼叫儲存在 `callable` 中的 vectorcall 函式。

成功時返回呼叫的結果,失敗時引發異常並返回 `NULL`。

在 3.9 版本中新增。

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

呼叫 `callable`,位置引數的傳遞方式與 vectorcall 協議完全相同,但關鍵字引數以字典 `kwdict` 的形式傳遞。`args` 陣列只包含位置引數。

無論內部使用哪種協議,都需要進行引數轉換。因此,只有當呼叫者已經準備好用於關鍵字引數的字典,但沒有用於位置引數的元組時,才應使用此函式。

在 3.9 版本中新增。

PyObject *PyObject_VectorcallMethod(PyObject *name, PyObject *const *args, size_t nargsf, PyObject *kwnames)
自 3.12 版本起成為 穩定 ABI 的一部分。

使用 vectorcall 呼叫約定呼叫方法。方法的名稱以 Python 字串 `name` 給出。被呼叫方法的物件是 `args[0]`,從 `args[1]` 開始的 `args` 陣列表示呼叫的引數。必須至少有一個位置引數。`nargsf` 是包括 `args[0]` 在內的位置引數的數量,如果 `args[0]` 的值可能臨時更改,則加上 PY_VECTORCALL_ARGUMENTS_OFFSET。關鍵字引數可以像 PyObject_Vectorcall() 中那樣傳遞。

如果物件具有 Py_TPFLAGS_METHOD_DESCRIPTOR 功能,這將呼叫未繫結方法物件,並以完整的 `args` 向量作為引數。

成功時返回呼叫的結果,失敗時引發異常並返回 `NULL`。

在 3.9 版本中新增。

呼叫支援 API

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

確定物件 `o` 是否可呼叫。如果物件可呼叫則返回 1,否則返回 0。此函式始終成功。