NumPy 2.0 迁移指南#

本文档包含一套关于如何更新代码以与 NumPy 2.0 协同工作的说明。它涵盖了 NumPy 的 Python 和 C API 中的更改。

注意

请注意,NumPy 2.0 也打破了二进制兼容性——如果您正在分发依赖 NumPy C API 的 Python 包的二进制文件,请参阅 NumPy 2.0 特定建议

Ruff 插件#

2.0 发布说明和本迁移指南中涵盖的许多更改,都可以通过专门的 Ruff 规则,即规则 NPY201,在下游代码中自动进行调整。

您应该安装 ruff>=0.4.8 并将 NPY201 规则添加到您的 pyproject.toml

[tool.ruff.lint]
select = ["NPY201"]

您也可以直接从命令行应用 NumPy 2.0 规则

$ ruff check path/to/code/ --select NPY201

NumPy 数据类型提升的更改#

NumPy 2.0 按照 NEP 50 的规定更改了提升(组合不同数据类型的结果)。有关此更改的详细信息,请参阅 NEP。它包含一个示例更改表和一个向后兼容性部分。

最大的向后兼容性更改是现在一致地保留了标量的精度。两个示例如下:

  • np.float32(3) + 3. 现在返回一个 float32,而以前返回一个 float64。

  • np.array([3], dtype=np.float32) + np.float64(3) 现在将返回一个 float64 数组。(标量更高的精度不会被忽略。)

对于浮点值,这可能导致与标量一起工作时结果精度降低。对于整数,可能出现错误或溢出。

为了解决这个问题,您可以显式地进行类型转换。很多时候,通过 int()float()numpy_scalar.item() 确保您使用 Python 标量,也可能是一个很好的解决方案。

要追踪更改,您可以启用对已更改行为发出警告(使用 warnings.simplefilter 将其作为错误抛出以获取回溯)。

np._set_promotion_state("weak_and_warn")

这在测试期间很有用。不幸的是,运行此操作可能会标记许多在实践中不相关的更改。

Windows 默认整数#

NumPy 使用的默认整数现在在所有 64 位系统上都是 64 位(在 32 位系统上是 32 位)。由于与 Python 2 相关的历史原因,它以前等同于 C long 类型。默认整数现在等同于 np.intp

大多数最终用户不应受到此更改的影响。一些操作将使用更多内存,但一些操作实际上可能会变得更快。如果您由于调用用编译语言编写的库而遇到问题,显式转换为 long 可能会有所帮助,例如使用:arr = arr.astype("long", copy=False)

如果用 C、Cython 或类似语言编写的与编译代码交互的库正在 C 侧使用 long 或等效类型,则可能需要更新以适应用户输入。在这种情况下,您可能希望使用 intp 并转换用户输入或同时支持 longintp(以更好地支持 NumPy 1.x)。在 C 或 Cython 中创建新的整数数组时,新的 NPY_DEFAULT_INT 宏将根据 NumPy 版本评估为 NPY_LONGNPY_INTP

请注意,NumPy 随机 API 不受此更改的影响。

C-API 更改#

由于过时或难以维护,某些定义被移除或替换。一些新的 API 定义在 NumPy 2.0 和 NumPy 1.x 之间运行时评估方式不同。有些定义在 numpy/_core/include/numpy/npy_2_compat.h 中(例如 NPY_DEFAULT_INT),可以全部或部分复制,以便在针对 NumPy 1.x 编译时可以使用这些定义。

如有必要,可以使用 PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION 来明确实现 NumPy 1.x 和 2.0 上的不同行为。(兼容头文件以兼容此类使用的方式定义了它。)

如果您需要其他解决方法,请告诉我们。

PyArray_Descr 结构已更改#

最有影响力的 C-API 更改之一是 PyArray_Descr 结构现在变得更加不透明,以允许我们添加额外的标志,使元素大小不受 int 大小的限制,并允许将来改进结构化 dtype,而不会给新的 dtype 带来字段负担。

仅使用类型编号和其他初始字段的代码不受影响。大多数代码希望主要访问 ->elsize 字段,当 dtype/descriptor 本身附属于数组时(例如 arr->descr->elsize),最好将其替换为 PyArray_ITEMSIZE(arr)

在不可能的情况下,需要新的访问函数

  • PyDataType_ELSIZEPyDataType_SET_ELSIZE(请注意,结果现在是 npy_intp 而不是 int)。

  • PyDataType_ALIGNMENT

  • PyDataType_FIELDSPyDataType_NAMESPyDataType_SUBARRAY

  • PyDataType_C_METADATA

Cython 代码应使用 Cython 3,在这种情况下,更改是透明的。(在仅为 NumPy 2 编译时,elsize 和 alignment 可通过结构体访问。)

为了同时使用 1.x 和 2.x 进行编译,如果您使用这些新的访问器,很不幸需要通过宏在本地定义它们,例如

#if NPY_ABI_VERSION < 0x02000000
  #define PyDataType_ELSIZE(descr) ((descr)->elsize)
#endif

或者将 npy2_compat.h 添加到您的代码库中,并在使用 NumPy 1.x 编译时明确包含它(因为它们是新的 API)。包含该文件对 NumPy 2 没有影响。

如果您需要帮助或提供的函数不足,请随时在 NumPy 中提出问题。

自定义用户 DType:现有用户 DType 现在必须使用 PyArray_DescrProto 来定义其 dtype 并稍微修改代码。请参阅 PyArray_RegisterDataType 中的注意事项。

功能已移至需要 import_array() 的头文件中#

如果您以前只包含了 ndarraytypes.h,您可能会发现某些功能不再可用,并且需要包含 ndarrayobject.h 或类似文件。当您将 npy_2_compat.h 导入到您自己的代码库中时,也需要此包含,以便在使用 NumPy 1.x 编译时可以使用新的定义。

以前不需要导入的功能包括

  • 访问 dtype 标志的函数:PyDataType_FLAGCHKPyDataType_REFCHK 和相关的 NPY_BEGIN_THREADS_DESCR

  • PyArray_GETITEMPyArray_SETITEM

警告

重要的是,当使用 npy_2_compat.h 头文件时,必须使用 import_array() 机制来确保 NumPy API 完全可访问。在大多数情况下,您的扩展模块可能已经调用了它。但是,如果还没有,我们已经添加了 PyArray_ImportNumPyAPI() 作为确保 NumPy API 被导入的更好方式。此函数在多次调用时是轻量级的,因此您可以将其插入到任何需要的地方(如果您希望避免在模块导入时设置它)。

最大维度数增加#

最大维度数(和参数)已增加到 64。这会影响 NPY_MAXDIMSNPY_MAXARGS 宏。最好审查它们的使用,我们通常鼓励您不要使用这些宏(尤其是 NPY_MAXARGS),以便 NumPy 的未来版本可以取消此维度限制。

NPY_MAXDIMS 在 C-API 中也用于表示 axis=None,包括 PyArray_AxisConverter。后者将返回 -2147483648 作为轴(最小整数值)。其他函数可能报错 AxisError: axis 64 is out of bounds for array of dimension,在这种情况下,您需要传入 NPY_RAVEL_AXIS 而不是 NPY_MAXDIMSNPY_RAVEL_AXISnpy_2_compat.h 头文件中定义,并依赖于运行时(在 NumPy 1.x 上映射到 32,在 NumPy 2.x 上映射到 -2147483648)。

复数类型 - 底层类型更改#

所有复数类型的底层 C 类型已更改为使用原生的 C99 类型。虽然这些类型的内存布局与 NumPy 1.x 中使用的类型保持相同,但 API 略有不同,因为不再可能直接访问字段(如 c.realc.imag)。

建议使用函数 npy_crealnpy_cimag(以及相应的 float 和 long double 变体)来检索复数的实部或虚部,因为这些函数在 NumPy 1.x 和 NumPy 2.x 中都有效。已添加新函数 npy_csetrealnpy_csetimag,以及兼容宏 NPY_CSETREALNPY_CSETIMAG(以及相应的 float 和 long double 变体),用于设置实部或虚部。

在 C++ 中,底层类型仍然是结构体(以上所有内容仍然有效)。

这会影响 Cython。建议始终使用原生 typedefs cfloat_tcdouble_tclongdouble_t,而不是 NumPy 类型 npy_cfloat 等,除非您必须与使用 NumPy 类型编写的 C 代码进行接口。您仍然可以使用 c.realc.imag 属性(使用原生 typedefs)编写 Cython 代码,但不能再在 Cython 的 c++ 模式中使用就地运算符 c.imag += 1

因为 NumPy 2 现在包含了 complex.h,所以使用名为 I 的变量的代码可能会看到以下错误:

现在,要使用名称 I 需要一个 #undef I

注意

NumPy 2.0.1 曾短暂包含 #undef I,以帮助尚未包含 complex.h 的用户。

命名空间更改#

在 NumPy 2.0 中,某些函数、模块和常量被移动或移除,以通过删除不必要或过时的功能并澄清 NumPy 的哪些部分被认为是私有的来使 NumPy 命名空间更易于使用。请参阅下表以获取迁移指导。对于大多数更改,这意味着将其替换为向后兼容的替代方案。

有关更多详细信息,请参阅 NEP 52 — NumPy 2.0 的 Python API 清理

主命名空间#

np 命名空间中约有 100 个成员已被弃用、移除或移至新位置。这样做是为了减少混乱并建立访问给定属性的唯一方式。下表显示了已移除的成员

已移除成员

迁移指南

add_docstring

它仍然作为 np.lib.add_docstring 可用。

add_newdoc

它仍然作为 np.lib.add_newdoc 可用。

add_newdoc_ufunc

这是一个内部函数,没有替代品。

alltrue

改用 np.all

asfarray

改用带浮点 dtype 的 np.asarray

byte_bounds

现在可在 np.lib.array_utils.byte_bounds 下使用

cast

改用 np.asarray(arr, dtype=dtype)

cfloat

改用 np.complex128

charrarray

它仍然作为 np.char.chararray 可用。

clongfloat

改用 np.clongdouble

compare_chararrays

它仍然作为 np.char.compare_chararrays 可用。

compat

没有替代品,因为 Python 2 不再受支持。

complex_

改用 np.complex128

cumproduct

改用 np.cumprod

DataSource

它仍然作为 np.lib.npyio.DataSource 可用。

deprecate

直接使用 warnings.warn 发出 DeprecationWarning,或使用 typing.deprecated

deprecate_with_doc

直接使用 warnings.warn 发出 DeprecationWarning,或使用 typing.deprecated

disp

改用您自己的打印函数。

fastCopyAndTranspose

改用 arr.T.copy()

find_common_type

改用 numpy.promote_typesnumpy.result_type。要实现 scalar_types 参数的语义,请使用 numpy.result_type 并传递 Python 值 00.00j

format_parser

它仍然作为 np.rec.format_parser 可用。

get_array_wrap

float_

改用 np.float64

geterrobj

改用 np.errstate 上下文管理器。

Inf

改用 np.inf

Infinity

改用 np.inf

infty

改用 np.inf

issctype

改用 issubclass(rep, np.generic)

issubclass_

改用内置的 issubclass

issubsctype

改用 np.issubdtype

mat

改用 np.asmatrix

maximum_sctype

改用特定的 dtype。您应避免依赖任何隐式机制,并在代码中明确选择一种类型中最大的 dtype。

NaN

改用 np.nan

nbytes

改用 np.dtype(<dtype>).itemsize

NINF

改用 -np.inf

NZERO

改用 -0.0

longcomplex

改用 np.clongdouble

longfloat

改用 np.longdouble

lookfor

直接搜索 NumPy 的文档。

obj2sctype

改用 np.dtype(obj).type

PINF

改用 np.inf

product

改用 np.prod

PZERO

改用 0.0

recfromcsv

改用 np.genfromtxt 并使用逗号分隔符。

recfromtxt

改用 np.genfromtxt

round_

改用 np.round

safe_eval

改用 ast.literal_eval

sctype2char

改用 np.dtype(obj).char

sctypes

改为显式访问 dtypes。

seterrobj

改用 np.errstate 上下文管理器。

set_numeric_ops

对于一般情况,请使用 PyUFunc_ReplaceLoopBySignature。对于 ndarray 子类,定义 __array_ufunc__ 方法并覆盖相关的 ufunc。

set_string_function

改用 np.set_printoptions 并使用格式化程序自定义打印 NumPy 对象。

singlecomplex

改用 np.complex64

string_

改用 np.bytes_

sometrue

改用 np.any

source

改用 inspect.getsource

tracemalloc_domain

现在可从 np.lib 获得。

unicode_

改用 np.str_

who

改用 IDE 变量浏览器或 locals()

如果表中不包含您正在使用但在 2.0 中已移除的项,则表示它是私有成员。您应该使用现有 API,或者在不可行的情况下,向我们提出恢复已移除条目的请求。

下表列出了已弃用的成员,这些成员将在 2.0 之后的版本中移除

已弃用成员

迁移指南

in1d

改用 np.isin

row_stack

改用 np.vstackrow_stackvstack 的别名)。

trapz

改用 np.trapezoidscipy.integrate 函数。

最后,已移除一组内部枚举。由于它们未在下游库中使用,我们不提供如何替换它们的信息。

[FLOATING_POINT_SUPPORT, FPE_DIVIDEBYZERO, FPE_INVALID, FPE_OVERFLOW, FPE_UNDERFLOW, UFUNC_BUFSIZE_DEFAULT, UFUNC_PYVALS_NAME, CLIP, WRAP, RAISE, BUFSIZE, ALLOW_THREADS, MAXDIMS, MAY_SHARE_EXACT, MAY_SHARE_BOUNDS]

numpy.lib 命名空间#

np.lib 中的大多数函数也存在于主命名空间中,那是它们的主要位置。为了明确如何访问每个公共函数,np.lib 现在为空,仅包含少数专门的子模块、类和函数

  • array_utilsformatintrospectmixinsnpyioscimathstride_tricks 子模块,

  • ArrayteratorNumpyVersion 类,

  • add_docstringadd_newdoc 函数,

  • tracemalloc_domain 常量。

如果您在访问 np.lib 中的属性时遇到 AttributeError,则应尝试从主 np 命名空间访问它。如果主命名空间中也缺少该项,则表示您正在使用私有成员。您应该使用现有 API,或者在不可行的情况下,向我们提出恢复已移除条目的请求。

numpy.core 命名空间#

np.core 命名空间现在正式设为私有,并已更名为 np._core。用户不应直接从 _core 中获取成员——而应使用主命名空间来访问相关属性。_core 模块的布局未来可能会在不通知的情况下更改,这与遵循弃用期策略的公共模块不同。如果主命名空间中也缺少某个项,则您应该使用现有 API,或者在不可行的情况下,向我们提出恢复已移除条目的请求。

ndarray 和标量方法#

np.ndarraynp.generic 标量类中的一些方法已被移除。下表提供了已移除成员的替代方案

已失效成员

迁移指南

newbyteorder

改用 arr.view(arr.dtype.newbyteorder(order))

ptp

改用 np.ptp(arr, ...)

setitem

改用 arr[index] = value

numpy.strings 命名空间#

已创建新的 numpy.strings 命名空间,其中大多数字符串操作都以 ufuncs 的形式实现。旧的 numpy.char 命名空间仍然可用,并且在可能的情况下,使用新的 ufuncs 以提高性能。我们建议今后使用 strings 函数。char 命名空间将来可能会被弃用。

其他更改#

关于 pickled 文件的注意事项#

NumPy 2.0 旨在加载使用 NumPy 1.26 创建的 pickle 文件,反之亦然。对于 1.25 及更早版本,加载 NumPy 2.0 pickle 文件将抛出异常。

适应 copy 关键字的更改#

copy 关键字行为的变化asarrayarrayndarray.__array__ 中,可能需要进行这些更改:

  • 使用 np.array(..., copy=False) 的代码在大多数情况下可以改为 np.asarray(...)。旧代码倾向于这样使用 np.array 是因为它比默认的 np.asarray 按需复制行为开销更小。现在情况已非如此,np.asarray 是首选函数。

  • 对于需要显式传递 None/False 意为“如果需要则复制”以兼容 NumPy 1.x 和 2.x 的代码,请参阅 scipy#20172 以获取如何操作的示例。

  • 对于非 NumPy 类似数组对象的任何 __array__ 方法,必须在签名中添加 dtype=Nonecopy=None 关键字——这同样适用于较旧的 NumPy 版本(尽管较旧的 NumPy 版本永远不会传入 copy 关键字)。如果将关键字添加到 __array__ 签名中,则对于

    • copy=True 和任何 dtype 值始终返回一个新副本,

    • copy=None 如果需要则创建副本(例如通过 dtype),

    • copy=False 绝不能创建副本。如果需要创建副本以返回 NumPy 数组或满足 dtype,则引发异常(ValueError)。

编写依赖 NumPy 版本的代码#

明确根据 numpy 版本进行分支的代码应该很少见——在大多数情况下,代码可以重写为同时兼容 1.x 和 2.0。但是,如果确实有必要,这里有一个建议的代码模式可以使用,利用 numpy.lib.NumpyVersion

# example with AxisError, which is no longer available in
# the main namespace in 2.0, and not available in the
# `exceptions` namespace in <1.25.0 (example uses <2.0.0b1
# for illustrative purposes):
if np.lib.NumpyVersion(np.__version__) >= '2.0.0b1':
    from numpy.exceptions import AxisError
else:
    from numpy import AxisError

此模式将正确运行,包括与 NumPy 发布候选版本一起,这在 2.0.0 发布期间非常重要。