对于下游包作者#

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

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

NumPy 使用一个标准的、PEP 440 兼容的版本方案:major.minor.bugfix.一个 major 版本的发布是非常不寻常的,如果发生,最可能表明 ABI 的破坏.NumPy 1.xx 版本从2006年到2023年发布;NumPy 2.0 在2024年初是第一个改变 ABI 的版本(对于边缘情况,ABI 的轻微破坏可能在次要版本中发生过几次).*Minor* 版本定期发布,通常每6个月一次.次要版本包含新特性、弃用和移除之前弃用的代码.*Bugfix* 版本的发布更为频繁;它们不包含任何新特性或弃用.

重要的是要知道,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 正确运行,但不能与旧版本一起运行.

针对 NumPy 主分支或预发布的测试#

对于依赖于 NumPy 的大型、积极维护的包,我们建议在 CI 中针对 NumPy 的开发版本进行测试.为了方便这一点,每晚构建的轮子在 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变化的早期警告,以便调整你的代码.

如果你想针对最新的 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 文档.

如果一个包直接使用 NumPy C API 或者它使用了某些依赖于它的其他工具,如 Cython 或 Pythran,NumPy 是该包的 构建时 依赖项.

默认情况下,NumPy 将公开一个向后兼容的 API,该 API 与支持当前最旧兼容 Python 版本的最旧 NumPy 版本兼容.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 包,请参见 这里.

截至目前,这通常就像包含::一样简单

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),你也可以固定当前的主版本以防止ABI破坏.请注意,对NumPy设置上限可能会 影响你的库与较新包一起安装的能力.

备注

SciPy 在其文档中提供了关于如何构建轮子以及处理其构建时和运行时依赖项的更多信息 这里.

NumPy 和 SciPy 的 wheel 构建 CI 也可能作为参考有用,可以在 这里找到 NumPy这里找到 SciPy.

NumPy 2.0 特定建议#

NumPy 2.0 是一个破坏 ABI 的版本,然而它确实包含了对构建在 2.0 和 1.xx 版本上都能工作的 wheel 的支持.重要的是要理解这一点:

  1. 当你在构建时使用 NumPy 1.xx 版本为你的包构建轮子时,这些 将无法工作 于 NumPy 2.0.

  2. 当你在构建时使用 NumPy 2.x 版本为你的包构建轮子时,这些 将兼容 NumPy 1.xx.

NumPy ABI 2.0 首次保证稳定的时间将是 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接口,也请考虑在你的元数据中添加相同的 numpy<2.0 要求,直到你确定你的代码已更新以适应2.0中的变化(即,当你已经测试过代码在 2.0.0rc1 上工作正常时).*理由:我们将进行一次重大的API清理,移除许多别名和已弃用/不推荐的对象(参见,例如* numpy-2-迁移指南 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

上述内容仅在 NumPy 2.0 在 PyPI 上可用时有效.如果你想针对 NumPy 2.0-dev 轮子进行测试,你必须使用 numpy 的夜间构建(见 这一部分 更上方)或从源代码构建 numpy.