- 科学计算
- 数据分析
【免费下载链接】numpy
The fundamental package for scientific computing with Python.
导读
NumPy 2.0 是一次具有里程碑意义的大版本升级,它同时破坏了 Python API 与 C API 的向后兼容性。本文基于本仓库的官方迁移指南(doc/source/numpy_2_0_migration_guide.rst),系统梳理了从 NumPy 1.x 迁移到 2.0 的全部要点:包括 NEP 50 标量提升规则变化、Windows 平台默认整数类型调整、PyArray_Descr结构体不透明化、复数类型底层 C99 化、以及主命名空间中约 100 个成员的移除与迁移方案。读完本文,你将掌握用 Ruff 的 NPY201 规则自动化扫描代码、手工适配 Python 与 C/Cython 扩展、以及编写同时兼容 1.x 与 2.x 代码的全部实战技巧。
注意:NumPy 2.0 同时破坏了二进制兼容性。如果你在分发依赖 NumPy C API 的 Python 包二进制文件,请务必阅读本文 C-API 变更 一节中关于 ABI 处理的内容。
一、用 Ruff 插件自动迁移:规则 NPY201
迁移工作量大、易遗漏,好在社区已经提供了自动化工具。2.0 发布说明与本文迁移指南中覆盖的大部分变更,都可以用 Ruff 的专用规则NPY201(numpy2-deprecation)自动适配到下游代码中。
安装与配置要求:需要ruff>=0.4.8,并在pyproject.toml中加入规则:
[tool.ruff.lint] select = ["NPY201"]也可以在命令行直接对指定代码目录执行检查:
$ ruff check path/to/code/ --select NPY201NPY201 会识别本文后面提到的绝大部分废弃成员(如np.alltrue、np.cumproduct、np.Inf等),并给出对应的迁移建议,是迁移到 2.0 的第一道自动化防线。
二、数据类型提升规则变更(NEP 50)
NumPy 2.0 按照 NEP 50(scalar promotion) 改变了不同数据类型组合时的提升(promotion)规则,详见 NEP 原文中的变更示例表与向后兼容性章节。
2.1 最大的兼容性破坏:标量精度被一致保留
最大的向后兼容性变更在于:标量(scalar)的精度现在会被一致地保留。两个典型例子:
np.float32(3) + 3.现在返回float32,而之前返回 float64;np.array([3], dtype=np.float32) + np.float64(3)现在返回float64数组(标量的更高精度不再被忽略)。
对浮点数而言,与标量混合运算时可能产生更低精度的结果;对整数而言,则可能出现错误或溢出。
2.2 解决方案
- 显式转换(cast):在混合运算时显式指定 dtype。
- 回归 Python 标量:很多时候最佳方案是用
int()、float()或numpy_scalar.item()确保操作的是 Python 标量,从而避免 NumPy 标量参与提升。
2.3 用警告追踪行为变化
你可以在测试期间开启“weak promotion + warning”模式,让发生行为变化的运算发出警告,甚至用warnings.simplefilter将其升级为异常以便获取回溯:
np._set_promotion_state("weak_and_warn")该模式对测试阶段定位受影响代码很有用。需要注意的是,运行它可能会标记出许多实际无关紧要的变更,需要结合人工判断筛选。
三、Windows 平台默认整数变更
3.1 变更内容
NumPy 的默认整数类型现在在所有 64 位系统上为64 位(32 位系统上为 32 位)。出于 Python 2 时代的历史原因,之前默认整数等价于 C 的long类型;现在默认整数等价于np.intp。
3.2 对用户的影响
大多数最终用户不受影响:部分操作会多占一些内存,但部分操作反而可能更快。如果你因为调用某个编译语言编写的库而遇到问题,可以显式转换回long:
arr = arr.astype("long", copy=False)3.3 对扩展库开发者的影响
用 C、Cython 或类似语言对接编译代码的库,如果 C 侧使用了long或等价类型,可能需要更新以适配用户输入。此时建议:
- 改用
intp并对用户输入做转换; - 或同时支持
long与intp(以更好兼容 NumPy 1.x)。
在 C 或 Cython 中创建新整数数组时,新的NPY_DEFAULT_INT宏会根据 NumPy 版本展开为NPY_LONG或NPY_INTP。仓库中该宏的定义位于 numpy/_core/include/numpy/npy_2_compat.h:在NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION时定义为NPY_INTP,在 1.x 下定义为NPY_LONG,而当同时兼容 1.x 与 2.x 编译时则采用运行时判断:
#define NPY_DEFAULT_INT \ (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? NPY_INTP : NPY_LONG)注意:NumPy 的 random API 不受此变更影响。
四、C-API 变更
2.0 移除或替换了一些过时、难以维护的定义,部分新 API 定义在 1.x 与 2.0 之间的运行时求值结果不同。这些定义集中在 numpy/_core/include/numpy/npy_2_compat.h,该头文件可以被整体或部分 vendoring(复制)到下游项目,以便在针对 NumPy 1.x 编译时也能获得这些定义。
需要针对 1.x 和 2.0 实现不同行为时,可用运行时判断:
if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) { /* 2.0 行为 */ }(compat 头文件已按此用法做了兼容性定义。)如果你还需要其他 workaround,可以向 NumPy 社区反馈。
4.1PyArray_Descr结构体已改变
这是影响最大的 C-API 变更之一:PyArray_Descr结构体变得更加不透明(opaque),以便未来增加新的 flags、让 itemsize 不再受int大小限制、改进结构化 dtype,并避免新 dtype 背负旧字段负担。
影响范围:
- 只使用类型编号(type number)等初始字段的代码不受影响;
- 大多数代码主要通过
->elsize访问字段——当 descriptor 附着在数组上时(如arr->descr->elsize),最佳替换方案是PyArray_ITEMSIZE(arr); - 无法替换时,必须使用新的访问器函数:
| 访问器 | 说明 |
|---|---|
PyDataType_ELSIZE/PyDataType_SET_ELSIZE | 元素大小(结果类型从int变为npy_intp) |
PyDataType_ALIGNMENT | 对齐要求 |
PyDataType_FIELDS/PyDataType_NAMES/PyDataType_SUBARRAY | 结构化 dtype 的字段、名称与子数组 |
PyDataType_C_METADATA | C 侧元数据 |
在仓库源码中,这些访问器由DESCR_ACCESSOR宏批量生成,见 numpy/_core/include/numpy/npy_2_compat.h,例如:
DESCR_ACCESSOR(ELSIZE, elsize, npy_intp, 0) DESCR_ACCESSOR(ALIGNMENT, alignment, npy_intp, 0) DESCR_ACCESSOR(SUBARRAY, subarray, PyArray_ArrayDescr *, 1) DESCR_ACCESSOR(NAMES, names, PyObject *, 1) DESCR_ACCESSOR(FIELDS, fields, PyObject *, 1) DESCR_ACCESSOR(C_METADATA, c_metadata, NpyAuxData *, 1)其中第 4 个参数(legacy_only)为 1 的访问器只在 legacy dtype 上有效,非 legacy dtype 会返回 0。同时注意PyDataType_SET_ELSIZE现在接收npy_intp参数,且同头文件还提供了PyDataType_ISLEGACY(dtype)等判断辅助。
Cython 用户:应使用 Cython 3,此时该变更对你是透明的(仅针对 NumPy 2 编译时,结构体访问对elsize和alignment依然可用)。
同时兼容 1.x 与 2.x 编译:若使用这些新访问器,需要二选一:
- 用宏在本地定义(1.x 下直接字段访问):
#if NPY_ABI_VERSION < 0x02000000 #define PyDataType_ELSIZE(descr) ((descr)->elsize) #endif- 把
npy_2_compat.h加入代码库,并在针对 NumPy 1.x 编译时显式包含它(这些访问器属于新 API;在 NumPy 2 下包含该文件没有任何副作用)。
自定义用户 DType:现有用户 dtype 必须改用PyArray_DescrProto来定义 dtype,并小幅修改代码,详见PyArray_RegisterDataType的说明。npy_2_compat.h在 1.x 编译环境下会将PyArray_DescrProto别名为PyArray_Descr(numpy/_core/include/numpy/npy_2_compat.h),从而平滑过渡。
4.2 功能移至需要import_array()的头文件
如果你之前只包含了ndarraytypes.h,会发现部分功能不再可用,需要包含ndarrayobject.h或类似头文件。在把npy_2_compat.hvendoring 到自己代码库时,该包含同样必要,以便针对 NumPy 1.x 编译时也能使用新定义。
此前不需要 import 的功能包括:
- 访问 dtype flags 的函数:
PyDataType_FLAGCHK、PyDataType_REFCHK,以及相关的NPY_BEGIN_THREADS_DESCR; PyArray_GETITEM和PyArray_SETITEM。
这些宏在 numpy/_core/include/numpy/ndarrayobject.h 中可以看到与PyDataType_ELSIZE、PyDataType_FLAGS的联动实现,例如:
#define PyDataType_FLAGCHK(dtype, flag) \ (PyDataType_FLAGS(dtype) & (flag)) #define PyDataType_REFCHK(dtype) \ PyDataType_FLAGCHK(dtype, NPY_ITEM_REFCOUNT)警告:使用
npy_2_compat.h头文件时,必须通过import_array()机制确保完整 NumPy API 可访问。大多数扩展模块应该已经调用过它。如果还没有,NumPy 新增了PyArray_ImportNumPyAPI()作为更推荐的方式——该函数可重复轻量调用,因此可以在任何需要的位置插入(如果你想避免在模块导入时一次性设置)。其实现见 numpy/_core/include/numpy/npy_2_compat.h,本质上是PyArray_API == NULL时的惰性import_array1(-1)。
4.3 最大维度数增加到 64
最大维度数(及参数个数)增加到64,影响NPY_MAXDIMS与NPY_MAXARGS宏。建议:
- 审查这两个宏的使用;
- 尽量不要使用它们(尤其是
NPY_MAXARGS),以便未来版本能移除维度数量限制。
NPY_MAXDIMS此前还被用作 C-API 中axis=None的哨兵值,包括PyArray_AxisConverter。现在后者返回-2147483648(最小的整数值)作为 axis。其他函数可能报错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(numpy/_core/include/numpy/npy_2_compat.h):
#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION #define NPY_RAVEL_AXIS NPY_MIN_INT /* 2.0: -2147483648 */ #define NPY_MAXARGS 64 #elif NPY_ABI_VERSION < 0x02000000 #define NPY_RAVEL_AXIS 32 /* 1.x: 32 */ #define NPY_MAXARGS 32 #else #define NPY_RAVEL_AXIS \ (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? NPY_MIN_INT : 32) #endif4.4 复数类型:底层类型变更
所有复数类型的底层 C 类型改为原生 C99 类型。虽然内存布局与 NumPy 1.x 使用的类型完全一致,但 API 略有不同:不能直接字段访问(如c.real或c.imag)。
推荐做法:
- 读取实部/虚部:使用
npy_creal和npy_cimag(以及对应的 float、long double 变体npy_crealf/npy_cimagf/npy_creall/npy_cimagl),这些函数在 1.x 与 2.x 下都能工作; - 设置实部/虚部:使用新增的
npy_csetreal和npy_csetimag,以及兼容宏NPY_CSETREAL与NPY_CSETIMAG(同样有 float、long double 变体)。
这些函数定义在 numpy/_core/include/numpy/npy_math.h,宏形式则定义在 numpy/_core/include/numpy/npy_2_complexcompat.h,例如:
static inline double npy_creal(const npy_cdouble z); static inline void npy_csetreal(npy_cdouble *z, const double r); #define NPY_CSETREAL(z, r) npy_csetreal(z, r) #define NPY_CSETIMAG(z, i) npy_csetimag(z, i)在 C++ 下,底层类型仍然是 struct(以上所有内容依然有效)。
对 Cython 的影响:
- 推荐始终使用原生 typedef
cfloat_t、cdouble_t、clongdouble_t,而不是 NumPy 类型npy_cfloat等——除非你必须对接用 NumPy 类型编写的 C 代码; - 使用原生 typedef 时仍可写
c.real、c.imag属性,但在 Cython 的 C++ 模式下不能再使用就地运算符(如c.imag += 1)。
I宏冲突:因为 NumPy 2 现在包含complex.h,使用名为I的变量的代码可能报错:
error: expected ')' before '__extension__' double I,要使用名字I,现在必须#undef I。(附注:NumPy 2.0.1 曾短暂加入#undef I以帮助尚未包含complex.h的用户。)
五、命名空间变更
NumPy 2.0 移除了部分函数、模块和常量,以让命名空间更友好:清除过时功能、明确哪些部分属于私有。详情见 NEP 52(Python API cleanup)。对大多数变更而言,迁移方式就是替换为向后兼容的替代品。
5.1 主命名空间(np)
主命名空间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_types或numpy.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 | 直接显式访问 dtype |
| seterrobj | 改用np.errstate上下文管理器 |
| set_numeric_ops | 一般情况改用PyUFunc_ReplaceLoopBySignature;对 ndarray 子类,定义__array_ufunc__方法并覆盖相关 ufunc |
| set_string_function | 改用np.set_printoptions的 formatter 参数自定义 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;若不可行,可以向 NumPy 社区请求恢复。
下表是被**废弃(deprecated)**的成员,将在 2.0 之后的某个版本移除:
| 废弃成员 | 迁移指引 |
|---|---|
| in1d | 改用np.isin |
| row_stack | 改用np.vstack(row_stack本来就是vstack的别名) |
| trapz | 改用np.trapezoid或scipy.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
5.2numpy.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 或向社区请求恢复。
5.3numpy.core命名空间
np.core现在正式成为私有命名空间,并更名为np._core。用户绝不应直接从_core取成员,而应通过主命名空间访问。_core模块的布局未来可能不通知地改变——这与遵循废弃周期策略的公共模块不同。如果主命名空间也没有你要的成员,同样应改用现有 API 或请求恢复。
5.4 ndarray 与标量方法
np.ndarray与np.generic标量类的少数方法被移除:
| 过期成员 | 迁移指引 |
|---|---|
| newbyteorder | 改用arr.view(arr.dtype.newbyteorder(order)) |
| ptp | 改用np.ptp(arr, ...) |
| setitem | 改用arr[index] = value |
5.5numpy.strings命名空间
NumPy 2.0 新建了numpy.strings命名空间,其中大部分字符串操作以ufunc形式实现。旧的numpy.char命名空间仍然可用,并尽可能复用新 ufunc 以获得更高性能。官方推荐今后使用numpy.strings中的函数,numpy.char可能在将来被废弃。
六、其他重要变更
6.1 关于 pickle 文件的兼容性
- NumPy 2.0 设计上可以加载 NumPy 1.26 创建的 pickle 文件,反之亦然;
- 对于 1.25 及更早版本,加载 NumPy 2.0 的 pickle 文件会抛出异常。
6.2 适配copy关键字行为变更
2.0 中np.asarray、np.array与ndarray.__array__的copy关键字行为发生了变化,可能需要如下调整:
- 大多数使用
np.array(..., copy=False)的代码可以改为np.asarray(...)。旧代码之所以这样写,是因为它比默认“按需复制”的np.asarray开销更小;如今这个前提已不成立,np.asarray是更推荐的选择; - 对于需要显式传递
None/False表达“按需复制”且同时兼容 1.x 与 2.x 的代码,可以参考 SciPy 的 PR 示例做法(scipy#20172)实现版本分派; - 对于任何非 NumPy 数组类对象的
__array__方法,签名中必须加入dtype=None和copy=None关键字——这在旧版 NumPy 下也能工作(旧版只是永远不会传入copy)。加入关键字后语义为:copy=True且任意dtype值:总是返回新副本;copy=None:仅在必要时(例如受dtype驱动)创建副本;copy=False:绝不创建副本;若需要副本才能返回 NumPy 数组或满足dtype,则抛出异常(ValueError)。
6.3 编写依赖 NumPy 版本的代码
大多数情况下无需显式分支判断numpy版本——代码可以同时兼容 1.x 和 2.0。但若确有必要,推荐使用numpy.lib.NumpyVersion(定义于 numpy/lib/_version.py,实现了完整的版本字符串比较语义,能正确处理 release candidate 等版本):
# 示例:AxisError 在 2.0 中不再位于主命名空间, # 而在 <1.25.0 中也不存在于 exceptions 命名空间 # (此处以 <2.0.0b1 为例演示): if np.lib.NumpyVersion(np.__version__) >= '2.0.0b1': from numpy.exceptions import AxisError else: from numpy import AxisError该模式能正确处理 NumPy 的 release candidate 版本,这在 2.0.0 发布周期内尤为重要。
七、迁移路线图总结
- 自动化扫描:安装
ruff>=0.4.8,用NPY201规则扫描全部代码,逐项修复主命名空间的废弃成员; - 测试期排查提升变化:在测试套件中临时开启
np._set_promotion_state("weak_and_warn"),结合warnings.simplefilter("error")定位受 NEP 50 影响的运算; - 检查平台相关代码:确认默认整数(Windows 上从
long变为intp)不会破坏与编译库的互操作; - C/Cython 扩展:重点审查
PyArray_Descr直接字段访问(改用访问器或PyArray_ITEMSIZE)、复数字段访问(改用npy_creal/npy_csetreal等)、NPY_MAXDIMS哨兵用法(改用NPY_RAVEL_AXIS)、import_array()包含关系,以及用户自定义 dtype 的PyArray_DescrProto适配; - 命名空间与 API 替换:按上文的三个表格逐项替换被移除、废弃与过期的成员;
- 二进制分发:将 numpy/_core/include/numpy/npy_2_compat.h(必要时连同 npy_2_complexcompat.h)vendoring 进项目,确保针对 1.x 编译的二进制也能在 2.0 运行时正确工作;
- 回归测试:用 NumPy 1.26 与 2.0 双版本跑通测试,确认 pickle 互读与
copy关键字语义符合预期。
如果你在迁移过程中遇到本指南未覆盖的场景、或现有访问器函数不足以解决问题,欢迎向 NumPy 提交 issue,社区会继续补充 workaround 与兼容方案。
- 科学计算
- 数据分析
【免费下载链接】numpy
The fundamental package for scientific computing with Python.
相关推荐
NumPy 2.0无缝迁移:ta-lib-python兼容性实战指南
NumPy 2.0无缝迁移:ta lib python兼容性实战指南 你是否在升级NumPy到2.0后遭遇ta lib python报错?本文将帮你10分钟完成
金融科技数据分析Cropper.js 2.0 迁移指南:从1.0到2.0的全面升级解析
Cropper.js 2.0 迁移指南:从1.0到2.0的全面升级解析 前言 Cropper.js 2.0 是一次重大的架构升级,采用了全新的组件化设计理念。本
前端UI组件图像处理Composer 2.0 升级指南:从1.x迁移到2.0的全面解析
Composer 2.0 升级指南:从1.x迁移到2.0的全面解析 前言 Composer 2.0 是一次重大版本更新,带来了许多架构改进和新特性。本文将从技术
包管理器开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考