公共 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
文件中声明,这些文件与相应的 DLL/SO 文件一起在二进制 SciPy 安装中分发。
应用程序二进制接口#
然而,在 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更改,从而破坏任何使用更改签名的代码,并且**应遵循**一般的弃用/移除政策。
cdef
声明的其他内容(例如struct
、enum
和类型)是 最终的。一旦在发布的 SciPy 版本中公开了 Cython API 中的声明,它就不能被更改。如果需要更改,则需要通过添加具有不同名称的新声明并删除旧声明来执行。
cdef
类在公共API中是 不允许 的(待定: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
你可以在相应的 .pyx
文件末尾使用 scipy._lib.deprecation.deprecate_cython_api
函数来进行弃用:
# 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
在此之后,Cython 模块在 cimport somefunc
时,将在导入时发出 DeprecationWarning。
无法弃用 Cython 数据结构和类型。但是,在 API 中所有使用它们的函数都被移除后,它们可以被移除,前提是已经经历了弃用周期。
整个 Cython 模块可以通过在顶层发出 DeprecationWarning 来类似地弃用 Python 模块。