对于下游软件包的作者#

本文档旨在解释编写依赖 NumPy 软件包的一些最佳实践.

理解 NumPy 的版本控制和 API/ABI 稳定性#

NumPy 使用标准的,符合 PEP 440 的版本控制方案: major.minor.bugfix . 主版本发布非常罕见,如果发生,则很可能表示 ABI 中断. NumPy 1.xx 版本从 2006 年到 2023 年发布; 2024 年初的 NumPy 2.0 是第一个更改 ABI 的版本(小的 ABI 中断可能发生在少数次要版本中). 次要版本定期发布,通常每 6 个月一次. 次要版本包含新功能,弃用以及删除先前弃用的代码. 错误修复版本发布得更加频繁; 它们不包含任何新功能或弃用.

重要的是要知道,NumPy 像 Python 自身和大多数其他著名的科学 Python 项目一样,不使用语义版本控制. 相反,向后不兼容的 API 更改至少需要两个版本的弃用警告. 有关更多详细信息,请参见 NEP 23 — Backwards compatibility and deprecation policy .

NumPy 具有 Python API 和 C API. C API 可以直接使用,也可以通过 Cython,f2py 或其他此类工具使用. 如果您的软件包使用 C API,则 NumPy 的 ABI(应用程序二进制接口)稳定性非常重要. NumPy 的 ABI 是向前兼容但不是向后兼容的. 这意味着:针对给定目标版本的 NumPy C API 编译的二进制文件仍可在较新的 NumPy 版本上正确运行,但不能在较旧的版本上运行.

模块也可以在 CPython’s abi3 mode 中安全地针对 NumPy 2.0 或更高版本构建,这允许针对 Python 的单个(最小支持的)版本构建,但向前兼容同一系列中的更高版本(例如, 3.x ). 这可以大大减少需要构建和分发的 wheels 的数量. 有关更多信息和示例,请参见 cibuildwheel docs .

针对 NumPy main 分支或预发布版本进行测试#

对于依赖 NumPy 的大型,积极维护的包,我们建议在 CI 中针对 NumPy 的开发版本进行测试.为了使这更容易,每日构建版本以 wheels 的形式在 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 中的变化,以便调整你的代码.

如果你想针对最新的 NumPy 每日构建版本测试你自己的 wheel 构建,并且你正在使用 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 documentation .

如果一个包直接使用 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 头文件(或等效的 -D 编译器标志)之前,在每个需要 NumPy C-API 的扩展模块中. 如果你需要使用新添加的 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 包,请参见 here .

到目前为止,通常只需包含以下内容:

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(直接或通过 Cython),您还可以锁定当前的 major 版本以防止 ABI 破坏.请注意,设置 NumPy 的上限可能会 affect the ability of your library to be installed alongside other, newer packages .

备注

SciPy 有更多关于它如何构建 wheel 以及如何处理其构建时和运行时依赖项的文档 here .

NumPy 和 SciPy wheel 构建 CI 也可用作参考,可以在 here for NumPyhere for SciPy .

NumPy 2.0 特定的建议#

NumPy 2.0 是一个 ABI 破坏性的版本,但它包含支持构建可在 2.0 和 1.xx 版本上运行的 wheel.重要的是要理解:

  1. 当您在构建时使用 NumPy 1.xx 版本为您的软件包构建 wheel 时,这些 wheel 将无法与 NumPy 2.0 一起使用.

  2. 当您在构建时使用 NumPy 2.x 版本为您的软件包构建 wheel 时,这些 wheel 将可以与 NumPy 1.xx 一起使用.

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

  1. 在您软件包的 main(开发)分支中,不要添加任何约束.

  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 表面,还要考虑将相同的 numpy<2.0 要求添加到您的元数据中,直到您确定您的代码已针对 2.0 中的更改进行了更新(即,当您测试了针对 2.0.0rc1 的工作时).理由:我们将进行重要的 API 清理,许多别名和已弃用/不推荐使用的对象将被删除(例如,参见 NumPy 2.0 迁移指南NEP 52 — Python API cleanup for NumPy 2.0 ),因此,除非您仅使用现代/推荐的函数和对象,否则您的代码可能至少需要进行一些调整.

  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-dev wheel 进行测试,则必须使用 numpy nightly build(请参阅前面 this section 中的部分)或从源代码构建 numpy.