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 并转换用户输入,或者同时支持 long 和 intp(以更好地支持 NumPy 1.x)。在 C 或 Cython 中创建新的整数数组时,新的 NPY_DEFAULT_INT 宏将根据 NumPy 版本评估为 NPY_LONG 或 NPY_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_ELSIZE和PyDataType_SET_ELSIZE(请注意,结果现在是npy_intp而不是int)。PyDataType_ALIGNMENTPyDataType_FIELDS,PyDataType_NAMES,PyDataType_SUBARRAYPyDataType_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_GETITEM和PyArray_SETITEM。
警告
重要的是,必须使用 import_array() 机制来确保在使用 npy_2_compat.h 头文件时,可以访问完整的 NumPy API。在大多数情况下,您的扩展模块可能已经调用了它。但是,如果没有,我们添加了 PyArray_ImportNumPyAPI() 作为确保导入 NumPy API 的首选方法。当多次调用此函数时,它开销很小,因此您可以将其插入到任何需要的地方(如果您希望避免在模块导入时进行设置)。
增加了最大维度数#
最大维度数(和参数数)已增加到 64。这会影响 NPY_MAXDIMS 和 NPY_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_MAXDIMS。 NPY_RAVEL_AXIS 定义在 npy_2_compat.h 头文件中,并且是运行时依赖的(在 NumPy 1.x 上映射到 32,在 NumPy 2.x 上映射到 -2147483648)。
复数类型 - 底层类型更改#
所有复数类型的底层 C 类型已更改为使用原生的 C99 类型。虽然这些类型的内存布局与 NumPy 1.x 中使用的类型相同,但 API 略有不同,因为不再可能直接访问字段(如 c.real 或 c.imag)。
建议使用函数 npy_creal 和 npy_cimag(以及相应的 float 和 long double 变体)来检索复数的实部或虚部,因为它们可以同时兼容 NumPy 1.x 和 NumPy 2.x。已添加新的函数 npy_csetreal 和 npy_csetimag,以及兼容宏 NPY_CSETREAL 和 NPY_CSETIMAG(以及相应的 float 和 long double 变体),用于设置实部或虚部。
在 C++ 下,底层类型仍然是一个 struct(以上所有内容仍然有效)。
这对 Cython 有影响。建议始终使用原生 typedef cfloat_t, cdouble_t, clongdouble_t 而不是 NumPy 类型 npy_cfloat 等,除非您必须与使用 NumPy 类型编写的 C 代码进行交互。您仍然可以使用 Cython 中的 c.real 和 c.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 |
仍然可以作为 |
add_newdoc |
仍然可以作为 |
add_newdoc_ufunc |
这是一个内部函数,没有替代品。 |
alltrue |
使用 |
asfarray |
使用 |
byte_bounds |
现在可以在 |
cast |
使用 |
cfloat |
使用 |
charrarray |
仍然可以作为 |
clongfloat |
使用 |
compare_chararrays |
仍然可以作为 |
compat |
没有替代品,因为 Python 2 不再受支持。 |
complex_ |
使用 |
cumproduct |
使用 |
DataSource |
仍然可以作为 |
deprecate |
直接使用 |
deprecate_with_doc |
直接使用 |
disp |
使用您自己的打印函数代替。 |
fastCopyAndTranspose |
使用 |
find_common_type |
使用 |
format_parser |
仍然可以作为 |
get_array_wrap |
|
float_ |
使用 |
geterrobj |
使用 np.errstate 上下文管理器代替。 |
Inf |
使用 |
Infinity |
使用 |
infty |
使用 |
issctype |
使用 |
issubclass_ |
使用内置的 |
issubsctype |
使用 |
mat |
使用 |
maximum_sctype |
使用特定的 dtype 代替。您应该避免依赖任何隐式机制,并在代码中显式选择一种类型的最大 dtype。 |
NaN |
使用 |
nbytes |
使用 |
NINF |
使用 |
NZERO |
使用 |
longcomplex |
使用 |
longfloat |
使用 |
lookfor |
直接搜索 NumPy 的文档。 |
obj2sctype |
使用 |
PINF |
使用 |
product |
使用 |
PZERO |
使用 |
recfromcsv |
使用 |
recfromtxt |
使用 |
round_ |
使用 |
safe_eval |
使用 |
sctype2char |
使用 |
sctypes |
显式访问 dtypes 代替。 |
seterrobj |
使用 np.errstate 上下文管理器代替。 |
set_numeric_ops |
对于一般情况,使用 |
set_string_function |
使用 |
singlecomplex |
使用 |
string_ |
使用 |
sometrue |
使用 |
source |
使用 |
tracemalloc_domain |
现在可以在 |
unicode_ |
使用 |
who |
使用 IDE 变量浏览器或 |
如果表格中没有您在使用但已在 2.0 中移除的条目,则意味着它是一个私有成员。您应该使用现有 API,或者在不可行的情况下,联系我们请求恢复被移除的条目。
下表显示了已弃用的成员,它们将在 2.0 之后的版本中移除。
已弃用的成员 |
迁移指南 |
|---|---|
in1d |
使用 |
row_stack |
使用 |
trapz |
使用 |
最后,一组内部枚举已被移除。由于它们未在下游库中使用,因此我们不提供有关如何替换它们的任何信息。
[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,scimath和stride_tricks子模块;Arrayterator和NumpyVersion类;add_docstring和add_newdoc函数;tracemalloc_domain常量。
如果您在访问 np.lib 中的属性时遇到 AttributeError,您应该尝试从主 np 命名空间访问它。如果某个条目在主命名空间中也丢失,那么您正在使用一个私有成员。您应该使用现有 API,或者在不可行的情况下,联系我们请求恢复被移除的条目。
numpy.core 命名空间#
np.core 命名空间现在被正式标记为私有,并已重命名为 np._core。用户永远不应直接从 _core 获取成员——相反,应使用主命名空间来访问相关属性。 _core 模块的布局可能会在未来发生变化而不另行通知,这与公共模块(遵循弃用期策略)不同。如果某个条目在主命名空间中也丢失,那么您应该使用现有 API,或者在不可行的情况下,联系我们请求恢复被移除的条目。
ndarray 和标量方法#
np.ndarray 和 np.generic 标量类中的一些方法已被移除。下表提供了被移除成员的替代方案。
已废弃的成员 |
迁移指南 |
|---|---|
newbyteorder |
使用 |
ptp |
使用 |
setitem |
使用 |
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, array 和 ndarray.__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=None和copy=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 发布期间很重要。