面向下游软件包作者#
本文档旨在解释编写依赖 NumPy 的软件包的一些最佳实践.
了解 NumPy 的版本控制和 API/ABI 稳定性#
NumPy 使用标准的,符合 PEP 440 的版本控制方案: major.minor.bugfix .主版本发布非常罕见,如果发生,很可能表明 ABI 中断.NumPy 1.xx 版本从 2006 年到 2023 年发布;2024 年初的 NumPy 2.0 是第一个更改 ABI 的版本(次要版本中的极少数情况可能发生过 ABI 中断).次要版本定期发布,通常每 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 版本上正确运行,但不能在较旧的版本上运行.
模块也可以在 CPython’s abi3 mode 中安全地针对 NumPy 2.0 或更高版本构建,这允许针对 Python 的单个(最低支持)版本进行构建,但在同一系列(例如, 3.x )中与更高版本向前兼容. 这可以大大减少需要构建和分发的 wheel 数量. 有关更多信息和示例,请参见 cibuildwheel docs .
针对 NumPy main 分支或预发布版本进行测试#
对于依赖 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 中对警告引发错误,可以是所有警告,或者至少是 DeprecationWarning 和 FutureWarning . 这会提前警告你有关 NumPy 中的更改,以便调整你的代码.
如果你想针对最新的 NumPy nightly build 测试你自己的 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 将公开一个 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 软件包,请参阅 here .
到目前为止,通常只需包含:
host:
- numpy
run:
- {{ pin_compatible('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 上设置上限可能会 affect the ability of your library to be installed alongside other, newer packages .
备注
SciPy 提供了更多关于如何构建 wheels 以及如何处理其构建时和运行时依赖项的文档 here .
NumPy 和 SciPy wheel 构建 CI 也可以用作参考,可以在 here for NumPy 和 here for SciPy 中找到.
NumPy 2.0 的具体建议#
NumPy 2.0 是一个 ABI 破坏性版本,但是它确实包含对构建在 2.0 和 1.xx 版本上都可以使用的 wheels 的支持.重要的是要理解:
当您在构建时使用 NumPy 1.xx 版本为您的软件包构建 wheels 时,这些 wheels 将不适用于 NumPy 2.0.
当您在构建时使用 NumPy 2.x 版本为您的软件包构建 wheels 时,这些 wheels 将与 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 的 large API surface,也请考虑将相同的
numpy<2.0要求添加到你的元数据中,直到你确定你的代码已针对 2.0 中的更改进行更新(即,当你已经测试了针对2.0.0rc1的工作情况).理由:我们将进行一次重要的 API 清理,许多别名和已弃用/不推荐使用的对象将被删除(例如,参见 NumPy 2.0 迁移指南 和 NEP 52 — Python API cleanup for NumPy 2.0 ),因此除非你只使用现代/推荐的函数和对象,否则你的代码可能至少需要一些调整.计划在第一个 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/etc.),它希望支持 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 build(参见上面 this section )或从源代码构建 numpy.