NumPy2要来了,但先别急!

  • B站:啥都会一点的研究生
  • 公众号:啥都会一点的研究生

如果你正在使用 Python 编写代码,那么很有可能正在直接或间接地使用 NumPy

如Pandas、Scikit-Image、SciPy、Scikit-Learn、AstroPy…这些都依赖于 NumPy

NumPy 2 是一个新的重要版本,候选版本将于 2024 年 2 月 1 日发布最终版本将在一两个月后发布重要的是,NumPy 2 向后不兼容,虽然不严重,但升级时可能需要做一些工作。这意味着需要确保在 NumPy 2 发布时你的应用程序不会崩溃

本文将介绍

  • 新版本可能导致应用程序崩溃的不同方式
  • 锁定软件包版本的重要性
  • 如何确保应用程序在准备就绪之前不安装 NumPy 2
  • 如何轻松升级代码以支持 NumPy 2

NumPy2 如何破坏应用程序

NumPy2可能对应用程序造成三种不同的影响,这是由新的不兼容依赖关系引起

  • 代码:如果应用程序代码直接使用了NumPy的API,那么代码可能会出现问题
  • 直接依赖项:代码中使用的库与NumPy 2不兼容
  • 间接/传递性依赖项:代码中使用的库的依赖关系可能不兼容

修复代码可能相对容易,但直接或间接依赖的库不在你的掌控之下,通常由社区志愿者维护

以一个例子来说明,截至2024年1月9日,scikit-image

  • 与NumPy2不兼容
  • 在其打包元数据中声明与NumPy>=1.22兼容

当NumPy2发布时,可能所有现有版本都将声称与NumPy 2兼容,但实际上至少部分会出现问题。如果运气好的话,维护人员可能会在NumPy2发布时发布新的兼容版本,但这些都是志愿者,可能无法按时完成。而这只是众多库中的一个例子

锁定软件包版本

一旦NumPy 2发布,当它以新的依赖项安装时应用程序可能会出现问题

简而言之,有两种依赖项配置:

  • 直接依赖项:在代码中直接导入的库的列表,即放在 pyproject.tomlsetup.py 中的依赖项
  • 锁定文件:所有直接或间接的依赖项(依赖项的依赖项)列表,锁定到具体版本。如 requirements.txt或其他依赖项工具使用的文件

在适当的时间间隔内,根据直接依赖项列表更新锁定文件

如何确保不安装NumPy 2

由于依赖项可能需要一些时间才能与NumPy 2兼容,因此可能希望坚持使用NumPy 1.x,这意味着要确保NumPy 2不会被安装。因此,无论是直接还是间接使用NumPy,都要确保依赖列表中的限制性依赖numpy<2

例如,如果使用 pyproject.toml 文件来配置 setuptools

# ...

[project]
dependencies = [
    "pandas",
    # For now, make sure NumPy 2 is not installed
    "numpy<2",
]

如果使用的是 setup.py,则

from setuptools import setup

setup(
    # ...,
    install_requires=[
        "pandas",
        # For now, make sure NumPy 2 is not installed
        "numpy<2",
    ],
)

等待依赖库支持 NumPy 2

如果最终依赖的所有库都支持 NumPy 2。记住,不仅需要验证直接依赖库,还需验证间接依赖库:查看锁定文件中的库列表

升级代码和依赖库

首先,从依赖关系中移除第 1 步中添加的 numpy<2 限制,因为不再需要

其次,如果直接使用 NumPy,需要更新一些代码用法,详见 NumPy 2 移植指南

https://numpy.org/devdocs/numpy_2_0_migration_guide.html

使用 Ruff 升级代码

迁移指南解释说可以使用 Ruff 连接器自动升级代码以支持 NumPy 2。如果还没有使用 Ruff,或许应该试试:它是 Flake8、PyLint 和许多其他工具的更快替代品。要安装它,请使用

pip install ruff

conda install conda-forge::ruff

假设有以下example.py代码

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` will be removed in NumPy 2.0. Use `numpy.complex128` instead.
example.py:4:35: NPY201 [*] `np.float_` will be removed in NumPy 2.0. Use `numpy.float64` instead.
Found 2 errors.
[*] 2 fixable with the `--fix` option.

之所以需要使用 --preview 选项,是因为该功能在 ruff 中仍不稳定。当准备好迁移时,也就是几个月后,这条 lint 规则将有望趋于稳定

可以添加 --fix 标志让 Ruff 为我们解决问题

$ ruff check --preview --fix --select NPY201 example.py
Found 2 errors (2 fixed, 0 remaining).

现在 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、poes 或 conda-lock 等工具,锁定所有直接或间接(“传递”)的依赖关系
  • 对于使用语义版本控制的库,即主要版本在不兼容的变更中发生变化的库,可以考虑添加抢先版本限制,如 numpy<2
  • 确保定期更新依赖关系

https://pythonspeed.com/articles/numpy-2/