PEP 384 – 定義穩定 ABI
- 作者:
- Martin von Löwis <martin at v.loewis.de>
- 狀態:
- 最終 (Final)
- 類型:
- 標準軌跡 (Standards Track)
- 建立日期:
- 2009年5月17日
- Python 版本:
- 3.2
- 公告歷史:
摘要
目前,每次功能版本更新都會在 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]。
參考文獻
版權
此文件已歸入公有領域 (public domain)。
來源: https://github.com/python/peps/blob/main/peps/pep-0384.rst
最後修改: 2025-02-01 08:55:40 GMT