面向下游包作者#
本文档旨在解释编写依赖于 NumPy 的包的一些最佳实践。
了解 NumPy 的版本控制和 API/ABI 稳定性#
NumPy 使用标准的、PEP 440 兼容的版本控制方案:major.minor.bugfix
。主版本发布非常罕见,如果发生,很可能表示 ABI 中断。NumPy 1.xx 版本从 2006 年发布到 2023 年;NumPy 2.0 于 2024 年初发布,是第一个更改 ABI 的版本(在一些小版本中可能发生过几次针对极端情况的 ABI 小幅更改)。次版本定期发布,通常每 6 个月发布一次。次版本包含新功能、弃用以及删除先前弃用的代码。修补程序版本发布得更加频繁;它们不包含任何新功能或弃用。
需要注意的是,NumPy 与 Python 本身以及大多数其他知名的科学 Python 项目一样,**不**使用语义版本控制。相反,向后不兼容的 API 更改需要至少发布两个版本才能发出弃用警告。有关更多详细信息,请参阅NEP 23 — 向后兼容性和弃用策略。
NumPy 既有 Python API,也有 C API。C API 可以直接使用,也可以通过 Cython、f2py 或其他此类工具使用。如果您的包使用 C API,则 NumPy 的 ABI(应用程序二进制接口)稳定性非常重要。NumPy 的 ABI 向前兼容,但向后不兼容。这意味着:针对给定目标版本的 NumPy C API 编译的二进制文件仍然可以在更新的 NumPy 版本上正确运行,但不能在旧版本上运行。
针对 NumPy 主分支或预发布版本进行测试#
对于依赖于 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 夜间构建版本测试您自己的 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 将公开一个与支持当前最旧兼容 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 头文件(或每个需要 NumPy C-API 的扩展模块中的等效 -D
编译器标志)之前。这主要在您需要使用新添加的 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.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 时,这些**将无法与** NumPy 2.0 一起使用。
当您使用 NumPy 2.x 版本在构建时构建包的 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 夜间构建版本(请参阅上面此部分)或从源代码构建 numpy。