公開 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 變更,這會破壞任何使用已更改簽名的程式碼,並且應遵循一般的棄用/移除策略。

  • 任何其他內容(例如 structenum 和類型)的 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