面向下游包作者#
本文档旨在解释一些编写依赖于 NumPy 的包的最佳实践。
理解 NumPy 的版本控制和 API/ABI 稳定性#
NumPy 使用标准的、符合PEP 440 的版本方案:major.minor.bugfix
。主版本(major)发布非常罕见,如果发生,很可能表示 ABI 中断。NumPy 1.xx 版本从 2006 年持续到 2023 年;2024 年初的 NumPy 2.0 是第一个更改 ABI 的版本(在次要版本中,可能几次出现针对极端情况的次要 ABI 中断)。次要版本(minor)定期发布,通常每 6 个月一次。次要版本包含新功能、弃用和删除先前弃用的代码。修补程序版本(bugfix)发布更加频繁;它们不包含任何新功能或弃用。
重要的是要知道,NumPy 与 Python 本身以及大多数其他知名的科学 Python 项目一样,**不**使用语义版本控制。相反,向后不兼容的 API 更改需要至少发布两个版本的弃用警告。更多详情,请参见 NEP 23 — 向后兼容性和弃用策略。
NumPy 同时具有 Python API 和 C API。C API 可以直接使用,也可以通过 Cython、f2py 或其他此类工具使用。如果你的包使用 C API,那么 NumPy 的 ABI(应用程序二进制接口)稳定性非常重要。NumPy 的 ABI 是向前兼容的,但不是向后兼容的。这意味着:针对给定目标版本的 NumPy C API 编译的二进制文件仍可在较新的 NumPy 版本中正确运行,但在较旧版本中则不行。
针对 NumPy 主分支或预发布版本进行测试#
对于依赖于 NumPy 的大型、积极维护的包,我们建议在 CI 中针对 NumPy 的开发版本进行测试。为了方便起见,夜间构建版本以 wheel 的形式提供,地址为 https://anaconda.org/scientific-python-nightly-wheels/。示例安装命令:
pip install -U --pre --only-binary :all: -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy
这有助于检测 NumPy 中需要在下一个 NumPy 版本发布之前修复的回归问题。此外,我们建议在此作业中对 CI 中的警告引发错误,无论是所有警告,或者至少是 DeprecationWarning
和 FutureWarning
。这可以让你及早收到有关 NumPy 更改的警告,以便调整你的代码。
如果你想将你自己的 wheel 构建版本与最新的 NumPy 夜间构建版本一起测试,并且你使用的是 cibuildwheel
,你可能需要在你的 CI 配置文件中添加类似以下内容:
CIBW_ENVIRONMENT: "PIP_PRE=1 PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple"
添加对 NumPy 的依赖#
构建时依赖#
注意
在 NumPy 1.25 之前,NumPy C-API 默认情况下**不是**以向后兼容的方式公开的。这意味着当使用早于 1.25 的 NumPy 版本编译时,你必须使用你希望支持的最旧版本进行编译。这可以通过使用 oldest-supported-numpy 来完成。请参阅 NumPy 1.24 文档。
如果一个包直接使用 NumPy C API,或者它使用其他依赖于它的工具(如 Cython 或 Pythran),那么 NumPy 就是该包的构建时依赖项。
默认情况下,NumPy 将公开一个与支持当前最旧兼容 Python 版本的最旧 NumPy 版本向后兼容的 API。NumPy 1.25.0 支持 Python 3.9 及更高版本,而 NumPy 1.19 是第一个支持 Python 3.9 的版本。因此,我们保证,在使用默认设置时,NumPy 1.25 将公开与 NumPy 1.19 兼容的 C-API。(确切的版本设置在 NumPy 内部头文件中)。
NumPy 也向前兼容所有次要版本,但主版本将需要重新编译(参见下面关于 NumPy 2.0 的具体建议)。
例如,可以通过添加以下内容来自定义默认行为:
#define NPY_TARGET_VERSION NPY_1_22_API_VERSION
在包含任何 NumPy 头文件(或每个需要 NumPy C-API 的扩展模块中的等效 -D
编译器标志)之前。这主要在你需要使用新添加的 API 但代价是不与旧版本兼容的情况下有用。
如果出于某种原因,你希望默认情况下为当前安装的 NumPy 版本编译,你可以添加:
#ifndef NPY_TARGET_VERSION
#define NPY_TARGET_VERSION NPY_API_VERSION
#endif
这允许用户通过 -DNPY_TARGET_VERSION
覆盖默认值。此定义对于每个扩展模块(import_array()
的使用)必须一致,也适用于 umath 模块。
当你针对 NumPy 编译时,你应该将正确的版本限制添加到你的 pyproject.toml
中(参见 PEP 517)。因为你的扩展程序将与 NumPy 的新主版本不兼容,并且可能与非常旧的版本不兼容。
对于 conda-forge 包,请参见 此处。
目前,通常只需包含以下内容:
host:
- numpy
run:
- {{ pin_compatible('numpy') }}
运行时依赖项和版本范围#
NumPy 本身和许多核心科学 Python 包都已就停止支持旧版 Python 和 NumPy 版本的时间表达成一致:NEP29。我们建议所有依赖于 NumPy 的包都遵循 NEP 29 中的建议。
对于运行时依赖项,请使用 setup.py
中的 install_requires
指定版本范围(假设你使用 numpy.distutils
或 setuptools
进行构建)。
大多数依赖于 NumPy 的库不需要设置上限版本:NumPy 谨慎地保持向后兼容性。
也就是说,如果你:(a)是一个保证频繁发布的项目,(b)使用了 NumPy API 的很大一部分,并且(c)担心 NumPy 的更改可能会破坏你的代码,你可以设置一个 <MAJOR.MINOR + N
的上限,其中 N 不小于 3,MAJOR.MINOR
是 NumPy 的当前版本 [*]。如果你使用 NumPy C API(直接使用或通过使用 NumPy 的 Cython 代码),你还可以固定当前主版本以防止 ABI 中断。请注意,在 NumPy 上设置上限可能会 影响你的库与其他较新包一起安装的能力。
注意
SciPy 有更多关于如何构建 wheel 以及处理其构建时和运行时依赖项的文档,此处。
NumPy 和 SciPy wheel 构建 CI 也可能作为参考,可以在 此处找到 NumPy 的 CI 和 此处找到 SciPy 的 CI。
NumPy 2.0 的具体建议#
NumPy 2.0 是一个 ABI 中断版本,但是它确实包含对构建可在 2.0 和 1.xx 版本上运行的 wheel 的支持。理解以下几点很重要:
当你使用构建时的 NumPy 1.xx 版本为你的包构建 wheel 时,这些 **将无法与 NumPy 2.0 兼容**。
当你使用构建时的 NumPy 2.x 版本为你的包构建 wheel 时,这些 **将与 NumPy 1.xx 兼容**。
NumPy 2.0 的 ABI 首次保证稳定的时间将是 2.0 的第一个候选版本(即 2.0.0rc1)的发布。我们处理你对 NumPy 的依赖的建议如下:
在你的包的主(开发)分支中,不要添加任何约束。
如果你依赖 NumPy C API(例如,通过在 C/C++ 中直接使用,或通过使用 NumPy 的 Cython 代码),请在你的包的依赖项元数据中为发行版/发行分支添加
numpy<2.0
要求。在发布 numpy2.0.0rc1
并可以将其作为目标之前,执行此操作。理由:NumPy C ABI 将在 2.0 中发生变化,因此任何依赖于 NumPy 的已编译扩展模块都将中断;它们需要重新编译。如果您依赖 NumPy Python API 的大量 API 接口,请考虑在确保您的代码已更新以适应 2.0 版本中的更改之前(例如,当您测试代码在
2.0.0rc1
下工作时),在元数据中添加相同的numpy<2.0
要求。原因:我们将进行重大的 API 清理,许多别名和已弃用/不推荐的对象将被移除(参见例如NumPy 2.0 迁移指南和NEP 52 — NumPy 2.0 的 Python API 清理),因此,除非您只使用现代/推荐的函数和对象,否则您的代码可能需要一些调整。计划在第一个 NumPy 2.0 预发布版本发布后不久(可能在 2024 年 2 月 1 日左右)发布您自己的依赖于
numpy
的包。原因:那时,您可以发布与 2.0 和 1.X 版本都兼容的包,因此您自己的最终用户将不会看到太多/任何中断(您希望pip install mypackage
在 NumPy 2.0 发布当天继续工作)。一旦
2.0.0rc1
可用,您可以按照下面概述的方式调整pyproject.toml
中的元数据。
有两种情况:您需要保持与 numpy 1.xx 的兼容性,同时也要支持 2.0;或者您可以放弃对您包的新版本的 numpy 1.xx 支持,而只支持 >=2.0。后者更简单,但对您的用户来说可能更具限制性。在这种情况下,只需将numpy>=2.0
(或numpy>=2.0.0rc1
)添加到您的构建和运行时要求中,就可以了。我们现在将重点关注“保持与 1.xx 和 2.x 的兼容性”,这稍微复杂一些。
使用 NumPy C API(通过 C/Cython 等)并希望支持 NumPy 1.23.5 及更高版本的包示例:
[build-system]
build-backend = ...
requires = [
# Note for packagers: this constraint is specific to wheels
# for PyPI; it is also supported to build against 1.xx still.
# If you do so, please ensure to include a `numpy<2.0`
# runtime requirement for those binary packages.
"numpy>=2.0.0rc1",
...
]
[project]
dependencies = [
"numpy>=1.23.5",
]
我们建议您至少有一个 CI 作业通过 wheel 进行构建/安装,然后针对包支持的最旧的 numpy 版本运行测试。例如
- name: Build wheel via wheel, then install it
run: |
python -m build # This will pull in numpy 2.0 in an isolated env
python -m pip install dist/*.whl
- name: Test against oldest supported numpy version
run: |
python -m pip install numpy==1.23.5
# now run test suite
只有在 PyPI 上提供 NumPy 2.0 后,上述方法才有效。如果您想针对 NumPy 2.0 开发版 wheel 进行测试,则必须使用 NumPy nightly build(参见上面本节)或从源代码构建 NumPy。