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 更改了提升(组合不同数据类型的 the result of combining dissimilar data types)规则。有关此更改的详细信息,请参阅 NEP。它包含一个示例更改表和一个向后兼容性部分。

最大的向后兼容性更改是标量(scalars)的精度现在得到了一致的保留。示例如下:

  • 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 结构体现在更加不透明,以便我们添加额外的标志,并允许项大小(itemsizes)不受 int 大小的限制,同时允许未来改进结构化 dtype,而不会给新 dtype 增加负担。

仅使用类型编号和其他初始字段的代码不受影响。大多数代码可能会主要访问 ->elsize 字段,当 dtype/描述符本身附加到数组时(例如 arr->descr->elsize),最好用 PyArray_ITEMSIZE(arr) 替换。

在无法做到这一点的地方,需要新的访问函数:

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

  • PyDataType_ALIGNMENT

  • PyDataType_FIELDS, PyDataType_NAMES, PyDataType_SUBARRAY

  • PyDataType_C_METADATA

Cython 代码应使用 Cython 3,在这种情况下,更改是透明的。(对于仅为 NumPy 2 编译,struct 访问可用于 elsize 和 alignment。)

对于同时为 1.x 和 2.x 编译,如果您使用这些新的访问器,不幸的是,您必须通过宏(macro)来本地定义它们,例如:

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

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

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

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

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

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

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

  • 用于访问 dtype 标志的函数:PyDataType_FLAGCHK, PyDataType_REFCHK,以及相关的 NPY_BEGIN_THREADS_DESCR

  • PyArray_GETITEMPyArray_SETITEM

警告

重要的是,必须使用 import_array() 机制来确保在使用 npy_2_compat.h 头文件时,可以访问完整的 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_AXIS 定义在 npy_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++ 下,底层类型仍然是一个 struct(以上所有内容仍然有效)。

这对 Cython 有影响。建议始终使用原生 typedef cfloat_t, cdouble_t, clongdouble_t 而不是 NumPy 类型 npy_cfloat 等,除非您必须与使用 NumPy 类型编写的 C 代码进行交互。您仍然可以使用 Cython 中的 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 的用户。

命名空间(Namespaces)的更改#

在 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

使用 np.asarray 并指定 float dtype 代替。

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 值 0, 0.0, 或 0j

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 对象打印的 formatter。

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.vstack 代替(row_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_utils, format, introspect, mixins, npyio, scimathstride_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 关键字更改#

asarray, arrayndarray.__array__copy 关键字行为更改 可能需要这些更改:

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

  • 对于需要以兼容 NumPy 1.x 和 2.x 的方式显式传递 None/False 来表示“按需复制”的代码,请参阅 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 发布期间很重要。