呼叫協定 (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_newtp_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 *(*