公開 Cython API#
截至 2020 年 4 月,SciPy 中的以下模組透過公開的 cdef
Cython API 宣告公開功能
scipy.linalg.cython_blas
scipy.linalg.cython_lapack
scipy.optimize.cython_optimize
scipy.special.cython_special
這使用了Cython 的宣告共享功能,其中共享的 cdef
項目在 *.pxd
檔案中宣告,這些檔案與二進制 SciPy 安裝中的相應 DLL/SO 檔案一起分發。
應用程式二進制介面 (Application Binary Interface)#
然而,在 SciPy 中使用這些功能需要 SciPy 貢獻者在維護應用程式二進制介面 (ABI) 穩定性方面格外小心。這類似於用 C 開發函式庫,並且與純 Python 中的向後兼容性運作方式不同。
與 Python 的主要區別在於,當使用者編寫的程式碼被編譯時,會使用標頭 .pxd
檔案中的宣告,但當使用者程式碼被導入時,它們也必須與 SciPy 中可用的內容相符。
使用者程式碼可以使用一個版本的 SciPy 進行編譯,而編譯後的二進制檔案(使用 .pxd
檔案中宣告的二進制介面)可以與系統上安裝的不同 SciPy 版本一起使用。如果介面不兼容,則會引發異常,或發生運行時記憶體損壞和崩潰。
在導入時,Cython 會檢查已安裝的 SciPy SO/DLL 檔案中函數的簽名是否與使用者在編譯期間使用的 .pxd
檔案中的簽名相符,如果不符,則會引發 Python 異常。如果 SciPy 程式碼結構正確(見下文),則僅對使用者程式碼中實際導入的函數執行此檢查。
我們依靠此功能來提供運行時安全檢查,這使得使用者更容易透過 Python 異常檢測到不兼容的 SciPy 版本,而不是難以追蹤的運行時崩潰。
ABI 穩定性目標#
SciPy 旨在 Cython 程式碼中維護 ABI 穩定性,在以下意義上:
透過使用一個版本的 SciPy 編譯使用者原始碼產生的二進制檔案,與任何其他可用於編譯原始碼的 SciPy 版本兼容。
嘗試在運行時使用不兼容版本的 SciPy 將導致在使用者模組導入時產生 Python 異常。
嘗試在編譯時使用不兼容版本的 SciPy 將導致 Cython 錯誤。
這表示使用者可以使用任何兼容版本的 SciPy 來編譯二進制檔案,而無需關注 ABI,即:
ABI 兼容性 = API 兼容性
Cython API 的向後/向前兼容性將使用與 Python API 類似的棄用/移除策略處理,請參閱棄用。
在 SciPy 中實作 ABI 穩定性#
在 SciPy 中開發 Cython API 的以下規則對於維持上述 ABI 穩定性目標是必要的
允許添加新的
cdef
宣告(函數、結構、類型等)。允許移除
cdef
宣告,但應遵循一般的棄用/移除策略。函數的
cdef
宣告可能會被更改。但是,更改會導致向後不兼容的 API 變更,這會破壞任何使用已更改簽名的程式碼,並且應遵循一般的棄用/移除策略。
任何其他內容(例如
struct
、enum
和類型)的cdef
宣告都是最終的。一旦宣告在已發布的 SciPy 版本中的公開 Cython API 中公開,就不得更改。如果需要更改,則需要透過添加具有不同名稱的新宣告並移除舊宣告來執行。
公開 API 中不允許使用
cdef
類別(待定:cdef 類別的向後兼容性需要更多研究,但在我們不確定時絕不允許使用)對於每個公開 API 模組(如
scipy.linalg.cython_blas
中),使用單一介面.pxd
宣告檔案。公開介面宣告檔案不應包含
cimport
語句。如果包含,Cython 的簽名檢查將檢查所有 cimported 函數,而不僅僅是使用者程式碼使用的函數,因此更改其中一個函數會破壞整個 API。如果需要資料結構,請在公開 API 中優先使用不透明結構。介面宣告不應包含任何結構成員的宣告。資料結構的分配、釋放和屬性訪問應使用函數完成。
棄用公開 Cython API#
例如,要棄用一個公開的 Cython API 函數
# scipy/something/foo.pxd
cdef public int somefunc()
# scipy/something/foo.pyx
cdef public int somefunc():
return 42
您可以添加使用 scipy._lib.deprecation.deprecate_cython_api
函數在相應的 .pyx
檔案末尾執行棄用
# scipy/something/foo.pyx
cdef public int somefunc():
return 42
from scipy._lib.deprecation import deprecate_cython_api
import scipy.something.foo as mod
deprecate_cython_api(mod, "somefunc", new_name="scipy.something.newfunc",
message="Deprecated in Scipy 1.5.0")
del deprecate_cython_api, mod
在此之後,cimport somefunc
的 Cython 模組將在導入時發出 DeprecationWarning。
沒有辦法棄用 Cython 資料結構和類型。但是,在 API 中使用它們的所有函數都被移除後,它們可以被移除,並經歷棄用週期。
整個 Cython 模組可以像 Python 模組一樣被棄用,方法是在頂層發出 DeprecationWarning。