NumPy 2.1.0 新特性解析:Python 3.13 自由线程支持、unstack 与 API 兼容性变化全指南
【免费下载链接】numpyThe fundamental package for scientific computing with Python.项目地址: https://gitcode.com/gh_mirrors/nu/numpy
NumPy 2.1.0 是 2.0 大规模重构之后的回归常规节奏版本,核心亮点是为即将到来的 Python 3.13 提供支持、初步支持自由线程(free-threaded)CPython,以及对 array-api 2023.12 标准的适配。本指南以官方发布说明(doc/source/release/2.1.0-notes.rst)为主线,结合仓库源码逐项解读新函数、弃用项、C API 变更与性能改进,帮助你在升级迁移时准确评估影响面。
读完本文你将掌握:如何使用新的np.unstack、override_repr、cumulative_sum/cumulative_prod等新 API;自由线程 Python 3.13 下 NumPy 的使用边界与 f2py 扩展的适配方法;以及copyto、clip、quantile等行为变化对既有代码的潜在影响。
版本总览与支持范围
NumPy 2.1.0 的主要发布目标有三个:
- 支持即将发布的 Python 3.13,并放弃对 Python 3.9 的支持;
- 初步支持自由线程(free-threaded)的 CPython 3.13;
- 支持 array-api 2023.12 标准。
本版本支持的 Python 版本范围为3.10–3.13。升级到 2.1.0 之前,请先确认你的运行环境满足这一 Python 版本约束。
新函数 numpy.unstack
np.unstack(array, axis=...)是 2.1.0 新增的数组拆分函数,用于沿指定轴将一个数组拆分为多个数组组成的元组(tuple),功能上作为 numpy.stack 的逆操作:
stack(unstack(x, axis=axis), axis=axis) == x其核心实现位于 numpy/_core/shape_base.py#L470-L519,通过array_function_dispatch派发,实现上等价于tuple(np.moveaxis(x, axis, 0))——因为对数组的迭代总是沿第一个轴进行。
import numpy as np arr = np.arange(24).reshape((2, 3, 4)) # 默认 axis=0,沿第一维拆成 2 个形状为 (3, 4) 的数组 np.unstack(arr) # (array([[ 0, 1, 2, 3], # [ 4, 5, 6, 7], # [ 8, 9, 10, 11]]), # array([[12, 13, 14, 15], # [16, 17, 18, 19], # [20, 21, 22, 23]])) np.unstack(arr, axis=1) # 沿第二维拆分 np.unstack(arr, axis=-1) # 沿最后一维拆分,与 axis=2 等价注意该函数的签名是位置参数x配合关键字axis(默认0),axis不支持负值以外的非常规语义,负索引与 Python 切片约定一致。
弃用项(Deprecations)
numpy.save 的 fix_imports 参数弃用
自 NumPy 1.17 起,numpy.save已改用不再支持 Python 2 的 pickle 协议,fix_imports关键字参数实际上一直处于被忽略状态,仅为向后兼容而保留。2.1.0 起该参数正式标记为弃用。如果你的代码中仍显式传入fix_imports,升级后应将其删除。
bincount 首个参数禁止传入非整数
将非整数输入作为numpy.bincount的第一个参数现已弃用。此前这类输入会被静默强制转换为整数,可能造成精度丢失而没有任何警告。从 2.1.0 开始这一行为被正式弃用,请在调用前自行完成显式转换(如np.bincount(arr.astype(np.intp)))。
过期弃用(Expired Deprecations)
以下在早期版本中已发出弃用警告的特性,在 2.1.0 中被正式移除或收紧:
- 标量与 0 维数组禁止用于
numpy.nonzero和numpy.ndarray.nonzero:此前对 0 维输入的行为含糊,现在直接报错; set_string_function内部函数被移除,对应的 C 函数PyArray_SetStringFunction被 stub 化(仅保留符号、不再可用)。
C API 变更:链接行为与扩展兼容性
API 符号默认隐藏但可自定义
NumPy 2.1.0 默认隐藏其导出的 API 符号,这意味着默认情况下无法再从其他库动态获取 NumPy API(Windows 上此前也从未支持过)。若你的代码出现与PyArray_API或PyArray_RUNTIME_VERSION相关的链接错误,可通过定义宏NPY_API_SYMBOL_ATTRIBUTE选择退出这一变更。
若问题源于某个上游头文件先包含了 NumPy 头文件,标准解法是:在包含该上游头文件之前先#include "numpy/ndarrayobject.h",并参照仓库中的 C API 使用文档自行导入 NumPy。
npy_3kcompat.h 中大量 shim 被移除
npy_3kcompat.h中大量面向旧版 Python 3 的兼容层与辅助函数被删除。若你的扩展代码依赖其中某个 shim,最稳妥的做法是将旧版该头文件直接 vendoring 进你的代码库。
PyUFuncObject 新增 process_core_dims_func 字段
对广义 ufunc(generalized ufunc)作者而言,这是重要增强:结构体PyUFuncObject新增字段process_core_dims_func,可设置为类型为PyUFunc_ProcessCoreDimsFunc的函数。该函数会在 ufunc 被调用时触发,允许 ufunc 作者:
- 校验 core 维度是否满足额外约束;
- 在用户未提供输出 core 维度大小时,为其设置输出维度。
这为自定义 gufunc 的维度检查与推断提供了官方扩展点。
新特性详解
初步支持自由线程 CPython 3.13
PEP 703 提出的 free-threaded CPython 3.13(实验性构建)移除了 GIL。NumPy 2.1 对该构建提供初步支持,为此修复了大量 C 层线程安全问题:此前 NumPy 使用大量 C 全局静态变量存储运行时缓存与状态,2.1 通过三种方式处理——重构以避免全局状态、将全局状态改为线程局部(thread-local)状态、或为共享状态加锁。
必须明确的边界:支持自由线程 Python 并不等于 NumPy 线程安全。
- 对 ndarray 的只读共享访问是安全的;
- NumPy 暴露共享可变状态,且没有为数组对象本身加锁来串行化对共享状态的访问;
- 在多线程中同时修改同一数组(例如同时调用 ufunc 与
resize方法)确实可能使 NumPy 崩溃。官方指引现阶段是"不要这样做"; - 对象数组(object arrays)需要特别注意:GIL 此前为对象数组访问提供了隐式锁,自由线程构建中不再有这一保护(相关讨论见 Issue #27199)。
建议:如果你有基于 multiprocessing 的工作流想迁移到线程,可以积极测试,但遇到可疑问题时应先确认在常规(非自由线程)CPython 3.13 构建下是否同样复现——许多线程 bug 在释放 GIL 的代码中也会出现,禁用 GIL 只是让这类问题更容易暴露。
reshape 支持 shape 与 copy 参数
numpy.reshape与numpy.ndarray.reshape新增shape与copy关键字参数。此前reshape只有在必要时才复制数据,现在可以通过copy=True/False显式控制复制语义,与 NumPy 2.0 起统一的关键字风格保持一致。
DLPack v1 支持
NumPy 现在支持 DLPack v1 协议,旧版本协议的支持将在未来弃用。DLPack 是跨框架共享张量内存的标准协议,这一升级让 NumPy 与 PyTorch、JAX 等生态的零拷贝互操作保持在最新标准之上。
asanyarray 支持 copy 与 device 参数
numpy.asanyarray新增copy与device参数,与numpy.asarray对齐。device参数用于指定目标设备(默认 CPU),是 NumPy 迈向多设备互操作(array-api 标准)的一部分。
打印选项新增 override_repr
numpy.printoptions、numpy.get_printoptions、numpy.set_printoptions新增override_repr选项,用于自定义repr(array)行为。在 numpy/_core/arrayprint.py#L1601-L1608 中可以看到实现:当override_repr被设置时,_array_repr_implementation会直接返回override_repr(arr)的结果,忽略其他所有打印选项。
import numpy as np def my_repr(arr): return f"<Array shape={arr.shape} sum={arr.sum()}>" with np.printoptions(override_repr=my_repr): print(np.arange(6).reshape(2, 3)) # <Array shape=(2, 3) sum=15>注意:override_repr与formatter一样,在每次调用set_printoptions时都会被重置(见 numpy/_core/arrayprint.py#L318-L324)。该选项只影响 ndarray 的 repr,不影响标量。
cumulative_sum 与 cumulative_prod:array-api 兼容的累加/累乘
新增numpy.cumulative_sum和numpy.cumulative_prod,作为numpy.cumsum/numpy.cumprod的 array-api 兼容替代(实现位于 numpy/_core/fromnumeric.py)。新函数的核心差异是支持固定初始值(include_initial):sum的初始值为 0,prod的初始值为 1,可将初始值包含在返回序列中,便于对齐累积语义。
clip 支持 max/min 关键字
numpy.clip新增max与min关键字参数,用于取代旧的a_min与a_max(旧参数仍可用,但新名更符合 NumPy 2 的命名风格)。同时行为修正:np.clip(a)或np.clip(a, None, None)现在返回输入数组的副本,而不再抛错。
astype 支持 device 参数
numpy.astype新增device参数,配合asanyarray/asarray的device参数,完善了跨设备类型转换的基础设施。
f2py 生成自由线程兼容扩展
f2py CLI 工具新增--freethreading-compatible标志(命令行解析位于 numpy/f2py/f2py2e.py#L108-L113 与 #L553):
python -m numpy.f2py -c --freethreading-compatible mymod.f90 -m mymod该标志生成的 C 扩展会被标记为与自由线程 CPython 解释器兼容,从而阻止解释器在导入该扩展时于运行时重新启用 GIL。注意:f2py不会分析 Fortran 代码的线程安全性,标记为兼容之前你必须自行确认被包装的 Fortran 代码在并发环境下是安全的。
改进(Improvements)
histogram 整数输入的自动分箱修正
对整数输入数据,当使用histogram_bin_edges提供的算法自动计算 bin 数量时,若计算出的 bin 宽度小于 1,会产生虚假的空 bin。2.1.0 修正了这一点:整数数据自动分箱现在保证 bin 大小不小于 1。
ndarray 形状类型参数协变且约束为 tuple[int, ...]
静态类型体系持续演进:ndarray的形状类型参数(shape type parameter)此前可以是任意值,现在被限制为tuple[int, ...],与ndarray.shape的运行时语义一致;同时该参数从不变(invariant)改为协变(covariant)。此变更同样适用于ndarray的子类型,如numpy.ma.MaskedArray。这会在类型检查(如 mypy、pyright)层面对使用泛型 ndarray 的代码产生更精确的推导,但协变放宽也可能暴露此前被掩盖的类型错误。
quantile 的 closest_observation 方法取最近偶数阶统计量
np.quantile(..., method="closest_observation")在边界情况下的"最近"定义从最近奇数阶统计量改为最近偶数阶统计量,与其它参考实现保持一致。如果你用该方法做过精确数值比对,升级后应对结果做一次回归验证。
lapack_lite 线程安全
lapack_lite是 NumPy 在构建时未检测到系统 BLAS/LAPACK 时使用的内置极简低性能 LAPACK 实现。此前它不是线程安全的:单线程使用无碍,但多线程并发执行线性代数操作可能因数据竞争导致错误结果甚至段错误。2.1.0 为lapack_lite添加了全局锁,串行化多线程下的访问,消除了这部分数据竞争。
printoptions 上下文管理器线程与异步安全
此前 printoptions 状态由 Python 与 C 全局变量混合存储。2.1.0 将其重构为存储在 PythonContextVar中——底层实现在 numpy/_core/printoptions.py#L14-L32,可以看到format_options = ContextVar("format_options", default=default_format_options_dict)。这使得printoptions上下文管理器在多线程与 asyncio 异步场景下都是安全的,不同协程/线程可以拥有各自独立的打印选项状态。
numpy.polynomial 类型注解
自 2.1.0 起,numpy.polynomial及其子包(chebyshev、hermite、laguerre、legendre、polynomial 等)的函数与便捷类均引入了 PEP 484 类型注解,类型检查器可以更可靠地验证多项式代码。
numpy.dtypes 类型提示改进
numpy.dtypes的类型注解更贴近运行时行为:原来使用的numpy.dtype类型别名被替换为专门的 dtype 子类型,并补齐了此前缺失的numpy.dtypes.StringDType注解。
性能改进
numpy.save 使用 pickle protocol 4
numpy.save保存 object dtype 数组时改用pickle protocol 4:支持超过 4GB 的 pickle 对象,且大型数组的保存速度提升约 5%。
OpenBLAS 构建调整(x86_64/i686 与 Windows)
- x86_64 与 i686 平台上的 OpenBLAS 使用更少的内核(kernels)构建,基于基准测试收敛为 5 个性能簇:
PRESCOTT NEHALEM SANDYBRIDGE HASWELL SKYLAKEX,缩小二进制体积的同时覆盖主流微架构; - Windows 上的 OpenBLAS 不再链接 quadmath,简化了许可合规;
- 因 Windows 上 OpenBLAS 的回归,OpenBLAS 0.3.26 多线程带来的性能提升被回退。
ma.cov 与 ma.corrcoef 显著加速
ma.cov与ma.corrcoef及其内部私有函数被重构,在大规模 masked arrays 上显著提速。但注意伴随的行为变化(见下文"Changes"),速度提升以轻微的结果差异为代价。
行为变化(Changes)
vecdot 成为 ufunc 后签名精度降低
numpy.vecdot现在是一个 ufunc,受 ufunc 类型桩(typing stub)限制,其类型签名不如此前精确。
floor/ceil/trunc 不再对整数输入做浮点转换
numpy.floor、numpy.ceil、numpy.trunc对整数与布尔 dtype 的输入数组不再执行到浮点 dtype 的转换——此前np.floor(np.arange(3))会返回 float 数组,2.1.0 起保持整数 dtype,结果语义更直观且避免不必要的类型提升。
ma.corrcoef 可能返回略有不同的结果
ma.corrcoef此前对每对变量使用逐对(pairwise)观测计算标准差,用于归一化由ma.cov估计的协方差——但ma.cov本身并不逐对处理观测,这种归一化并不必要。2.1.0 改用每个变量各自的标准差归一化:
- 显著减少墙钟时间(wall time);
- 当一对变量的观测不完全对齐时,相关系数估计会与旧版略有不同;
- 其余场景结果不变,包括无掩码值时与
corrcoef返回相同的相关矩阵。
如果你依赖ma.corrcoef的精确数值(如测试断言),建议核对新旧结果。
copyto 与 full 的转换安全性修正(NEP 50)
copyto现在正确遵循 NEP 50 并应用其转换安全性规则。Python 整数到 NumPy 整数、Python float 到 NumPy float 的转换现在即使可能赋值失败或精度丢失,也被视为"safe"。具体变化:
import numpy as np # 1) 此前是 unsafe/same-kind 转换,现在总是抛错; # 若确实需要 unsafe 转换,请传入数组或 NumPy 标量 np.copyto(np.zeros(3, np.int8), 1000) # 现在总是 raise # 2) safe 转换下由 TypeError 变为 OverflowError np.copyto(np.zeros(3, np.uint8), 1000, casting="safe") # 现在抛出 OverflowError(此前因 same-kind 抛 TypeError) # 3) 溢出行为变化:float32 装不下 1e300,safe 转换下溢出为 inf np.copyto(np.zeros(3, np.float32), 1e300, casting="safe") # 现在得到 inf(此前抛 TypeError) # 4) 仅使用 dtype 判断:NumPy 标量不再"看值"判断 np.copyto(np.zeros(3, np.float32), np.float64(3.0), casting="safe") # raise np.copyto(np.zeros(3, np.int8), np.int64(100), casting="safe") # raise最后两条是关键语义变化:旧版会检查100是否能放入int8数组,新规则只看 dtype 转换关系,因此 NumPy 标量(或 0 维数组)赋值在 safe 模式下更严格。这一改动让copyto、full、full_like与 NumPy 2 的完整行为对齐。升级后应重点检查依赖 safe casting 的数值流水线代码。
升级建议小结
- Python 版本:确认运行在 3.10–3.13,尽早规划离开 3.9;
- 编译型扩展:检查是否依赖被隐藏的 API 符号(必要时定义
NPY_API_SYMBOL_ATTRIBUTE)、npy_3kcompat.h中的旧 shim、以及PyUFuncObject的新字段扩展点; - 数值行为:重点回归
copyto/full的 casting 规则、ma.corrcoef的归一化变化、quantile(closest_observation)的取整变化、floor/ceil/trunc的 dtype 保持; - 新 API 采纳:
unstack、override_repr、cumulative_sum/cumulative_prod、reshape(copy=...)等新能力可直接提升代码简洁度与可读性; - 自由线程 Python:若计划使用,先明确只读共享安全、可变共享需自行加锁的边界,并对 object arrays 额外小心。
【免费下载链接】numpyThe fundamental package for scientific computing with Python.项目地址: https://gitcode.com/gh_mirrors/nu/numpy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考