公共Cython接口

自2020年4月起,本网站中的以下模块通过公共 cdef Cython API声明:

  • scipy.linalg.cython_blas

  • scipy.linalg.cython_lapack

  • scipy.optimize.cython_optimize

  • scipy.special.cython_special

这是用来 Cython's declaration sharing features ,共享位置 cdef 项在中声明 *.pxd 与二进制SciPy安装中相应的DLL/SO文件一起分发的文件。

应用程序二进制接口

然而,在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稳定性

为了维护上述ABI稳定性目标,在SciPy中开发Cython API时需要遵循以下规则:

  • 添加新的 cdef 声明(函数、结构、类型等) 是允许的

  • 删除 cdef 声明 是允许的 ,但是 应该紧随其后 一般弃用/删除策略。

  • cdef 函数的声明 可能会更改

    但是,更改会导致向后不兼容的API更改,该更改会破坏使用更改的签名的任何代码,并且 应该紧随其后 一般弃用/删除策略。

  • cdef 任何其他内容的声明(例如 structenum 和类型)是 最终版 。一旦在发布的SciPy版本的公共CythonAPI中暴露声明, 它不能更改

    如果需要更改,则需要通过添加具有不同名称的新声明并删除旧声明来执行更改。

  • cdef 类是 不允许 在公共API中(待定:cdef类的向后兼容性需要更多的研究,但当我们不确定时一定不能允许)

  • 对于每个公共API模块(如 scipy.linalg.cython_blas ),使用单一界面 .pxd 声明文件。

    公共接口声明文件 不应该 包含 cimport 结算单。如果是这样的话,Cython的签名检查将检查所有cimport函数,而不仅仅是用户代码使用的函数,因此更改其中一个函数会破坏整个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

在此之后,Cython模块 cimport somefunc ,将发出一个 DeprecationWarning 在导入时。

不推荐使用Cython数据结构和类型。但是,在API中使用它们的所有函数都已经过弃用循环后,可以将其删除。

整个Cython模块可以与Python模块类似地弃用,方法是发出 DeprecationWarning 在顶层。