目录
NumPy 2 即将发布:防止代码破坏,更新你的代码
如果你正在使用 Python 编写科学计算或数据科学代码,很可能你直接或间接地使用了 NumPy。Pandas、Scikit-Image、SciPy、Scikit-Learn、AstroPy 等许多包都依赖于 NumPy。
NumPy 2 是一个新的主要版本,预计在 2024 年 2 月 1 日发布候选版本,并在一个月或两个月后发布正式版本。重要的是,它是向后不兼容的;虽然不是重大变化,但足以需要一些工作来升级。这意味着你需要确保你的应用程序在 NumPy 2 发布时不会崩溃。
在本文中,我们将介绍:
- 新版本可能破坏你的应用程序的不同方式。
- 关于固定依赖项重要性的快速提醒。
- 如何确保你的应用程序在你准备好之前不会安装 NumPy 2。
- 如何轻松升级你的代码以支持 NumPy 2。
NumPy 2 可能如何破坏你的应用程序
一个新的、不兼容的依赖项可能会对你的应用程序产生三种影响:
- 你的代码:如果你在应用程序代码中直接使用 NumPy API,你的代码可能会崩溃。
- 直接依赖项:你直接在代码中使用的库可能与 NumPy 2 不兼容。
- 间接/传递依赖项:你使用的库的依赖项可能不兼容。
修复你的代码应该足够简单,但你直接或间接依赖的库不在你的控制范围内,而且通常由志愿者维护。
举个例子,截至 2024 年 1 月 9 日,scikit-image:
- 与 NumPy 2 不兼容。
- 在其打包元数据中声明它适用于
numpy>=1.22
。
当 NumPy 2 发布时,所有现有版本都会声称与 NumPy 2 兼容,但实际上至少部分功能会失效。幸运的话,维护者可能会在 NumPy 2 发布时推出一个新的兼容版本,但这些都是志愿者,他们可能无法按时完成。而这只是众多库中的一个。
回顾:为什么你需要固定依赖项
这类问题是为什么你需要“固定”应用程序依赖项的众多原因之一:确保你只安装一组特定的、固定的依赖项。如果没有可重现的依赖项,一旦 NumPy 2 发布,你的应用程序在安装新依赖项时可能会崩溃。
简而言之,你有两组依赖配置:
- 直接依赖列表:你直接在代码中导入的库的列表,限制较松。这是你放在
pyproject.toml
或setup.py
中的依赖项列表。 - 锁定文件:你依赖的所有依赖项(直接或间接)的列表,固定到特定版本。这可能是
requirements.txt
,或者根据你使用的工具不同,可能是其他文件。
第一步:确保不安装 NumPy 2
由于你的依赖项可能需要一些时间才能与 NumPy 2 兼容,最初你可能希望坚持使用 NumPy 1.x。这意味着确保不会安装 NumPy 2。因此,无论你是直接还是间接使用 NumPy,请确保在你的依赖项列表中添加一个限制性的 numpy<2
依赖项。
例如,如果你使用 pyproject.toml
文件来配置 setuptools
,它可能看起来像这样:
# ...
[project]
dependencies = [
"pandas",
# 暂时确保不安装 NumPy 2
"numpy<2",
]
如果你使用 setup.py
,它可能看起来像这样:
from setuptools import setup
setup(
# ...,
install_requires=[
"pandas",
# 暂时确保不安装 NumPy 2
"numpy<2",
],
)
第二步:等待依赖项支持 NumPy 2
最终,你依赖的所有库都将支持 NumPy 2。记住,你不仅需要验证直接依赖项,还需要验证间接依赖项:检查锁定文件中的库列表。
第三步:升级你的代码和依赖项
首先,从你的依赖项中移除你在第一步中添加的 numpy<2
限制,因为它将不再必要。
其次,如果你直接使用 NumPy,你需要更新一些代码用法,如 NumPy 2 迁移指南 中所述。
使用 Ruff 升级你的代码
迁移指南解释说,升级你的代码以支持 NumPy 2 可以使用 Ruff 自动完成。如果你还没有使用 Ruff,你可能应该使用它:它是 Flake8、PyLint 和其他许多工具的更快替代品。要安装它,请运行 pip install ruff
或 conda install conda-forge::ruff
。
现在,假设我们有以下模块:
import numpy as np
arr1 = np.array([1 + 3j, 2], dtype=np.cfloat)
arr2 = np.array([2.0, 3.0], dtype=np.float_)
我们可以使用 ruff
来查找与 NumPy 2 的不兼容性:
$ ruff check --preview --select NPY201 example.py
example.py:3:36: NPY201 [*] `np.cfloat` 将在 NumPy 2.0 中移除。请使用 `numpy.complex128` 替代。
example.py:4:35: NPY201 [*] `np.float_` 将在 NumPy 2.0 中移除。请使用 `numpy.float64` 替代。
发现 2 个错误。
[*] 2 个错误可通过 `--fix` 选项修复。
--preview
选项是必要的,因为这仍然是ruff
中的一个不稳定功能。当你准备迁移时,几个月后,这个 lint 规则应该会稳定下来。
我们可以添加 --fix
标志,让 Ruff 自动修复问题:
$ ruff check --preview --fix --select NPY201 example.py
发现 2 个错误(2 个已修复,0 个未修复)。
现在 example.py
看起来像这样:
import numpy as np
arr1 = np.array([1 + 3j, 2], dtype=np.complex128)
arr2 = np.array([2.0, 3.0], dtype=np.float64)
做好准备!
NumPy 已经保持了很长时间的 1.x 版本的向后兼容性,但所有库最终都会发生向后不兼容的更改。这就是为什么你应该:
- 使用锁定文件来固定所有依赖项(直接或间接),使用诸如
pip-tools
、pipenv
、poetry
或conda-lock
等工具。 - 对于使用语义版本控制的库(即主要版本在不兼容更改时发生变化),考虑添加预置的版本限制,如
numpy<2
。 - 确保你定期更新依赖项,以免落后。