面向下游软件包作者#

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

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

NumPy 使用标准的、PEP 440 兼容的版本控制方案:major.minor.bugfix 版本发布非常罕见,如果发生,很可能表明 ABI 发生了中断。NumPy 的 1.xx 版本发布于 2006 年至 2023 年;NumPy 2.0 于 2024 年初发布,是第一个更改 ABI 的版本(在次要版本发布中可能发生过几次细微的 ABI 中断)。次要 版本定期发布,通常每 6 个月一次。次要版本包含新功能、弃用以及对先前已弃用代码的移除。Bugfix 版本发布频率更高;它们不包含任何新功能或弃用。

需要注意的是,NumPy 与 Python 本身以及大多数其他知名的科学 Python 项目一样,**不** 使用语义化版本控制。相反,向后不兼容的 API 更改需要至少在两个版本发布中发出弃用警告。更多详细信息,请参阅 NEP 23 — 向后兼容性和弃用策略

NumPy 同时提供了 Python API 和 C-API。C-API 可以直接访问,也可以通过 Cython 或 f2py 等工具访问。如果您的软件包使用了 C-API,了解 NumPy 的应用程序二进制接口 (ABI) 兼容性非常重要:NumPy 的 ABI 是向前兼容的,但不是向后兼容的。这意味着针对旧版本 NumPy 编译的二进制文件仍然可以与新版本一起工作,但针对新版本编译的二进制文件不一定能与旧版本一起工作。

模块也可以安全地针对 NumPy 2.0 或更高版本以 CPython 的 abi3 模式 构建,该模式允许针对单个(最低支持)Python 版本进行构建,但在同一系列(例如 3.x)的更高版本中保持向前兼容。这可以大大减少需要构建和分发的 wheel 的数量。有关更多信息和示例,请参阅 cibuildwheel 文档

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

对于大型、积极维护且依赖于 NumPy 的软件包,我们建议在 CI 中针对 NumPy 的开发版本进行测试。为了方便起见,在 https://anaconda.org/scientific-python-nightly-wheels/ 提供了 wheel 形式的 nightly 构建。示例安装命令

pip install -U --pre --only-binary :all: -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy

这有助于在下一个 NumPy 版本发布之前检测到需要修复的 NumPy 回归。此外,我们建议在 CI 中为此任务将所有警告都视为错误,或者至少将 DeprecationWarningFutureWarning 视为错误。这能让您提前获知 NumPy 中的更改,以便调整您的代码。

如果您想针对最新的 NumPy nightly 构建测试自己的 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 与支持 NumPy 当前支持的最旧 Python 版本的最早 NumPy 版本向后兼容。例如,NumPy 1.25.0 支持 Python 3.9 及以上版本;支持 Python 3.9 的最早 NumPy 版本是 1.19。因此,我们保证 NumPy 1.25 在使用默认设置时,其 C-API 与 NumPy 1.19 兼容。(确切版本在 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 软件包,请参阅 此处,了解在使用 C API 时如何声明对 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 在如何构建 wheel 和处理其构建时和运行时依赖项方面有更详细的文档,请在此处 查看

NumPy 和 SciPy wheel 构建 CI 也可作为参考,可以在 NumPySciPy 的 GitHub 仓库中找到。

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. 在您的软件包的主(开发)分支中,不要添加任何限制。

  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 — 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

上述方法仅在 NumPy 2.0 可在 PyPI 上使用后才有效。如果您想针对 NumPy 2.0-dev wheel 进行测试,您必须使用 numpy nightly 构建(请参阅上面本节)或从源代码构建 numpy。