Following system colour scheme - Python 增強提案 Selected dark colour scheme - Python 增強提案 Selected light colour scheme - Python 增強提案

Python 增強提案 (Python Enhancement Proposals)

PEP 384 – 定義穩定 ABI

作者:
Martin von Löwis <martin at v.loewis.de>
狀態:
最終 (Final)
類型:
標準軌跡 (Standards Track)
建立日期:
2009年5月17日
Python 版本:
3.2
公告歷史:


目錄

重要資訊

本 PEP 為歷史文件。最新且規範性的說明文件,現可參閱 C API 穩定性(使用者文件)以及 變更 Python 的 C API(開發文件)。

×

關於如何提出變更建議,請參閱 PEP 1

摘要

目前,每次功能版本更新都會在 Windows 上為 Python DLL 引入一個新的名稱,並可能導致 Unix 上擴充模組的不相容。本 PEP 提議定義一組穩定的 API 函式,保證在 Python 3 的生命週期內可用,並確保跨版本間的二進位檔案相容性。只要擴充模組與嵌入 Python 的應用程式僅使用此穩定 ABI,就能在不同的功能版本下運作。

原理

ABI 不相容的主要來源是記憶體結構佈局的變更。例如,字串駐留 (string interning) 的運作方式,或用於表示物件大小的資料型別,在 Python 2.x 的生命週期中都發生了變化。因此,若擴充模組直接存取字串、列表或元組的欄位,一旦其程式碼未經重新編譯便載入到新版的解譯器中,就會導致崩潰:其他欄位的偏移量可能已變更,導致擴充模組存取到錯誤的資料。

在某些情況下,不相容性僅影響解譯器的內部物件,例如框架 (frame) 或程式碼物件。例如,行號的表示方式在 2.x 的生命週期中有所改變,區域變數的儲存方式亦然(由於閉包的引入)。即使大多數應用程式可能從未使用過這些物件,變更它們仍需要修改 PYTHON_API_VERSION。

在 Linux 上,ABI 的變更通常不是太大的問題:系統會提供預設的 Python 安裝,且許多擴充模組已針對該版本預先編譯。如果需要額外的模組或不同的 Python 版本,使用者通常可以在系統上自行編譯,從而得到使用正確 ABI 的模組。

在 Windows 上,同時安裝多個不同 Python 版本的情況很常見,且擴充模組是由其作者編譯,而非終端使用者。為了降低 ABI 不相容的風險,Python 目前會在每次功能發佈時引入一個新的 DLL 名稱 pythonXY.dll,無論 ABI 是否真的存在不相容。

透過本 PEP,將能減少二進位擴充模組對特定 Python 功能版本的依賴,並使嵌入 Python 的應用程式能夠在不同的版本下運作。

規範

ABI 規範分為兩部分:API 規範(指定哪些函式(群組)可用於該 ABI)以及連結規範(指定要連結哪些程式庫)。實際的 ABI(記憶體中結構的佈局、函式呼叫慣例)未明確指定,但由編譯器決定。作為建議,針對選定的平台推薦使用特定的 ABI。

隨著 Python 的演進,將會加入新的 ABI 函式。使用這些函式的應用程式將需要最低版本的 Python;本 PEP 未提供機制讓這類應用程式在 Python 程式庫過舊時進行後退處理 (fall back)。

術語

希望使用此 ABI 的應用程式與擴充模組,以下統稱為「應用程式」。

標頭檔與前處理器定義

應用程式僅應包含標頭檔 Python.h(在包含任何系統標頭檔之前),或者選擇性地先包含 pyconfig.h,再包含 Python.h。

在應用程式編譯期間,必須定義前處理器巨集 Py_LIMITED_API。這樣做將隱藏所有不屬於 ABI 的定義。

結構 (Structures)

應用程式僅能存取下列結構與結構欄位

  • PyObject (ob_refcnt, ob_type)
  • PyVarObject (ob_base, ob_size)
  • PyMethodDef (ml_name, ml_meth, ml_flags, ml_doc)
  • PyMemberDef (name, type, offset, flags, doc)
  • PyGetSetDef (name, get, set, doc, closure)
  • PyModuleDefBase (ob_base, m_init, m_index, m_copy)
  • PyModuleDef (m_base, m_name, m_doc, m_size, m_methods, m_traverse, m_clear, m_free)
  • PyStructSequence_Field (name, doc)
  • PyStructSequence_Desc (name, doc, fields, sequence)
  • PyType_Slot (見下文)
  • PyType_Spec (見下文)

用於存取這些欄位的巨集 (Py_REFCNT, Py_TYPE, Py_SIZE) 也對應用程式開放。

下列型別開放使用,但屬於不透明型別(即不完整型別)

  • PyThreadState
  • PyInterpreterState
  • struct _frame
  • struct symtable
  • struct _node
  • PyWeakReference
  • PyLongObject
  • PyTypeObject

型別物件 (Type Objects)

型別物件的結構無法直接供應用程式存取;宣告「靜態」型別物件已不再可行(針對使用此 ABI 的應用程式)。取而代之的是,型別物件必須動態建立。為了允許輕鬆建立型別(特別是能夠輕易填寫函式指標),下列結構與函式開放使用

typedef struct{
  int slot;    /* slot id, see below */
  void *pfunc; /* function pointer */
} PyType_Slot;

typedef struct{
  const char* name;
  int basicsize;
  int itemsize;
  unsigned int flags;
  PyType_Slot *slots; /* terminated by slot==0. */
} PyType_Spec;

PyObject* PyType_FromSpec(PyType_Spec*);

要指定一個 slot,必須提供唯一的 slot id。新的 Python 版本可能會引入新的 slot id,但 slot id 永遠不會被回收。Slot 可能會被棄用,但在整個 Python 3.x 期間將持續受到支援。

Slot id 的命名方式與 Python 3.1 中儲存這些指標的結構欄位名稱相同,並加上 Py_ 前綴(例如 Py_tp_dealloc 而非僅 tp_dealloc)

  • tp_dealloc, tp_getattr, tp_setattr, tp_repr, tp_hash, tp_call, tp_str, tp_getattro, tp_setattro, tp_doc, tp_traverse, tp_clear, tp_richcompare, tp_iter, tp_iternext, tp_methods, tp_base, tp_descr_get, tp_descr_set, tp_init, tp_alloc, tp_new, tp_is_gc, tp_bases, tp_del
  • nb_add nb_subtract nb_multiply nb_remainder nb_divmod nb_power nb_negative nb_positive nb_absolute nb_bool nb_invert nb_lshift nb_rshift nb_and nb_xor nb_or nb_int nb_float nb_inplace_add nb_inplace_subtract nb_inplace_multiply nb_inplace_remainder nb_inplace_power nb_inplace_lshift nb_inplace_rshift nb_inplace_and nb_inplace_xor nb_inplace_or nb_floor_divide nb_true_divide nb_inplace_floor_divide nb_inplace_true_divide nb_index
  • sq_length sq_concat sq_repeat sq_item sq_ass_item sq_contains sq_inplace_concat sq_inplace_repeat
  • mp_length mp_subscript mp_ass_subscript

下列欄位在型別定義期間無法設定:- tp_dict tp_mro tp_cache tp_subclasses tp_weaklist tp_print - tp_weaklistoffset tp_dictoffset

typedef 定義

除了上述結構的 typedef 外,下列 typedef 也開放使用。將其納入 ABI 意味著底層型別在該平台上不得更改(即使它可能在不同平台間有所差異)。

  • Py_uintptr_t Py_intptr_t Py_ssize_t
  • unaryfunc binaryfunc ternaryfunc inquiry lenfunc ssizeargfunc ssizessizeargfunc ssizeobjargproc ssizessizeobjargproc objobjargproc objobjproc visitproc traverseproc destructor getattrfunc getattrofunc setattrfunc setattrofunc reprfunc hashfunc richcmpfunc getiterfunc iternextfunc descrgetfunc descrsetfunc initproc newfunc allocfunc
  • PyCFunction PyCFunctionWithKeywords PyNoArgsFunction PyCapsule_Destructor
  • getter setter
  • PyOS_sighandler_t
  • PyGILState_STATE
  • Py_UCS4

特別值得注意的是,Py_UNICODE 不再作為 typedef 開放,因為同一 Python 版本在同一平台上可能會根據其使用的編碼單元寬度(窄字元或寬字元)而有不同的定義。需要存取 Unicode 字串內容的應用程式,可將其轉換為 wchar_t。

函式與函式型巨集

預設情況下,除非以下已排除,否則所有函式皆可使用。函式是否有說明文件並不影響其可用性。

函式型巨集(特別是欄位存取巨集)仍然適用於應用程式,但會被替換為函式呼叫(除非其定義僅參考 ABI 的功能,例如各種 _Check 巨集)

ABI 函式宣告將不會變更其參數或回傳型別。若簽章有變更必要,將引入一個新的函式。若新函式在原始碼層級相容(例如僅回傳型別變更),可能會加入別名巨集,以便在重新編譯應用程式時將呼叫重新導向至新函式。

若無法繼續提供舊函式,該函式可能會被棄用,隨後移除,導致使用該函式的應用程式失效。

排除的函式

所有以 _Py 開頭的函式皆不對應用程式開放。此外,所有預期參數型別對應用程式不可見的函式也排除在 ABI 之外,例如 PyAST_FromNode(其預期接收 node*)。

下列標頭檔中宣告的函式不屬於 ABI 的一部分

  • bytes_methods.h
  • cellobject.h
  • classobject.h
  • code.h
  • compile.h
  • datetime.h
  • dtoa.h
  • frameobject.h
  • funcobject.h
  • genobject.h
  • longintrepr.h
  • parsetok.h
  • pyarena.h
  • pyatomic.h
  • pyctype.h
  • pydebug.h
  • pytime.h
  • symtable.h
  • token.h
  • ucnhash.h

此外,預期接收 FILE* 的函式也不屬於 ABI,以避免在 Windows 上依賴特定版本的 Microsoft C 執行時期 DLL。

模組與型別的初始化函式 (initializer) 與終結函式 (finalizer) 不可用(PyByteArray_Init、PyOS_FiniInterrupts 以及所有以 _Fini 或 _ClearFreeList 結尾的函式)。

若干處理解譯器實作細節的函式不可用

  • PyInterpreterState_Head, PyInterpreterState_Next, PyInterpreterState_ThreadHead, PyThreadState_Next
  • Py_SubversionRevision, Py_SubversionShortBranch

PyStructSequence_InitType 不可用,因為它要求呼叫者提供靜態型別物件。

Py_FatalError 將會從 pydebug.h 移動至其他標頭檔(例如 pyerrors.h)。

可用函式的確切清單列於 python3.dll 的 Windows 模組定義檔案中 [1]

全域變數

代表型別與例外情況的全域變數對應用程式開放。此外,巨集中參考到的選定全域變數(如 Py_True 與 Py_False)亦開放使用。

全域變數定義的完整清單列於 python3.def 檔案中 [1];宣告為 DATA 者即為變數。

其他巨集

所有定義符號常數的巨集對應用程式開放;其數值將不會變更。

此外,下列巨集亦開放使用

  • Py_BEGIN_ALLOW_THREADS, Py_BLOCK_THREADS, Py_UNBLOCK_THREADS, Py_END_ALLOW_THREADS

緩衝區介面 (Buffer Interface)

緩衝區介面(Py_buffer 型別、bf_getbuffer 與 bf_releasebuffer 等 slot)已從 ABI 中省略,因為 Py_buffer 結構的穩定性目前尚未明確。未來版本中可考慮將其納入 ABI。

簽章變更

目前有許多函式預期傳入特定結構,儘管呼叫者通常只有 PyObject*。這些函式已修改為將 PyObject* 作為參數;這會導致目前明確轉型至參數型別的應用程式出現警告。這些函式包括 PySlice_GetIndices、PySlice_GetIndicesEx、PyUnicode_AsWideChar 與 PyEval_EvalCode。

連結 (Linkage)

在 Windows 上,應用程式應連結至 python3.dll;屆時將提供匯入程式庫 python3.lib。此 DLL 會透過 /export 連結器選項將其所有 API 函式重導向至完整的解譯器 DLL,即 python3y.dll。

在 Unix 系統上,ABI 通常由 python 可執行檔本身提供。若擴充模組使用 Py_LIMITED_API 編譯,PyModule_Create 會被修改為傳入 3 作為 API 版本;API 版本的檢查機制將接受 3 或當前的 PYTHON_API_VERSION 作為符合標準。若 Python 被編譯為共用程式庫,它將同時安裝為 libpython3.so 與 libpython3.y.so;符合本 PEP 的應用程式應連結至前者(擴充模組可繼續選擇不連結至 libpython 共用物件,而是依賴執行時期連結)。ABI 版本可符號化地透過 PYTHON_ABI_VERSION 取得。

同樣在 Unix 上,PEP 3149 的 abi<PYTHON_ABI_VERSION> 標籤被接受用於擴充模組的檔案名稱。目前不會檢查以此方式命名的檔案是否真的僅限於有限 API,且由於 distutils 的程式碼凍結,distutils 將不會加入建置此類檔案的支援。

實作策略

本 PEP 將在一個分支中實作 [2],允許使用者檢查其模組是否符合 ABI。為避免使用者必須重寫型別定義,將提供一個用於轉換包含型別定義的 C 原始碼的指令碼 [3]

參考文獻


來源: https://github.com/python/peps/blob/main/peps/pep-0384.rst

最後修改: 2025-02-01 08:55:40 GMT