面向下游包作者#

本文档旨在解释一些编写依赖于 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 中的警告引发错误,无论是所有警告,或者至少是 DeprecationWarningFutureWarning。这可以让你及早收到有关 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.distutilssetuptools 进行构建)。

大多数依赖于 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 的支持。理解以下几点很重要:

  1. 当你使用构建时的 NumPy 1.xx 版本为你的包构建 wheel 时,这些 **将无法与 NumPy 2.0 兼容**。

  2. 当你使用构建时的 NumPy 2.x 版本为你的包构建 wheel 时,这些 **将与 NumPy 1.xx 兼容**。

NumPy 2.0 的 ABI 首次保证稳定的时间将是 2.0 的第一个候选版本(即 2.0.0rc1)的发布。我们处理你对 NumPy 的依赖的建议如下:

  1. 在你的包的主(开发)分支中,不要添加任何约束。

  2. 如果你依赖 NumPy C API(例如,通过在 C/C++ 中直接使用,或通过使用 NumPy 的 Cython 代码),请在你的包的依赖项元数据中为发行版/发行分支添加 numpy<2.0 要求。在发布 numpy 2.0.0rc1 并可以将其作为目标之前,执行此操作。理由:NumPy C ABI 将在 2.0 中发生变化,因此任何依赖于 NumPy 的已编译扩展模块都将中断;它们需要重新编译。

  3. 如果您依赖 NumPy Python API 的大量 API 接口,请考虑在确保您的代码已更新以适应 2.0 版本中的更改之前(例如,当您测试代码在2.0.0rc1下工作时),在元数据中添加相同的numpy<2.0 要求。原因:我们将进行重大的 API 清理,许多别名和已弃用/不推荐的对象将被移除(参见例如NumPy 2.0 迁移指南NEP 52 — NumPy 2.0 的 Python API 清理),因此,除非您只使用现代/推荐的函数和对象,否则您的代码可能需要一些调整。

  4. 计划在第一个 NumPy 2.0 预发布版本发布后不久(可能在 2024 年 2 月 1 日左右)发布您自己的依赖于numpy 的包。原因:那时,您可以发布与 2.0 和 1.X 版本都兼容的包,因此您自己的最终用户将不会看到太多/任何中断(您希望pip install mypackage在 NumPy 2.0 发布当天继续工作)。

  5. 一旦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。