面向下游软件包作者#
本文档旨在解释编写依赖于 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 中为此任务将所有警告都视为错误,或者至少将 DeprecationWarning 和 FutureWarning 视为错误。这能让您提前获知 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.distutils 或 setuptools 进行构建)。
大多数依赖 NumPy 的库不需要设置上限版本:NumPy 会谨慎地保持向后兼容性。
即便如此,如果您(a)是一个保证频繁发布的项目,(b)使用了 NumPy API 的大部分表面,并且(c)担心 NumPy 的更改会破坏您的代码,您可以设置一个上限版本为 <MAJOR.MINOR + N,其中 N 不小于 3,而 MAJOR.MINOR 是当前的 NumPy 版本 [*]。如果您使用 NumPy C-API(直接或通过 Cython),您也可以固定当前主版本以防止 ABI 中断。请注意,设置 NumPy 的上限版本可能会影响您的库与其他更新的包并存安装的能力。
NumPy 2.0 特定建议#
NumPy 2.0 是一个 ABI 中断性版本,但它支持构建在 2.0 和 1.xx 版本上都能工作的 wheel。重要的是要理解:
当您在构建时使用 NumPy 1.xx 版本为您的软件包构建 wheel 时,这些 wheel **将无法**与 NumPy 2.0 一起工作。
当您在构建时使用 NumPy 2.x 版本为您的软件包构建 wheel 时,这些 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 表面,也请考虑在您的元数据中添加相同的
numpy<2.0要求,直到您确定您的代码已针对 2.0 中的更改进行了更新(即,您已针对2.0.0rc1测试过一切正常)。*理由:我们将进行重大的 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
上述方法仅在 NumPy 2.0 可在 PyPI 上使用后才有效。如果您想针对 NumPy 2.0-dev wheel 进行测试,您必须使用 numpy nightly 构建(请参阅上面本节)或从源代码构建 numpy。