PAYATHON 2026

np.vectorize 为何将数组中的 np.uint8 转为 int,却保留列表元素类型?

数据小许

类型变化发生在输入转换为 object 数组时:uint8 数值数组转换后,元素会变成 Python int;如果直接用包含 np.uint8 对象的列表构造 object 数组,则可以保留这些对象。

题中的两次调用经过了不同的取值路径,因此先看到 numpy.uint8,随后看到 int。调用顺序与 NumPy 版本有关,题中的打印结果并非所有版本的固定行为。

为什么数组输入会先得到 np.uint8,再得到 int?

以 NumPy 1.26.4 的默认路径为例,未设置 otypes、signature,且 cache=False 时,执行过程如下:

  1. 使用 np.asarray 处理输入,取第一个元素调用 f,推断输出类型和输出数量。
  2. 将原始输入转换为 dtype=object 的数组,由 np.frompyfunc 创建的 ufunc 逐元素调用 f。
  3. 将计算结果转换为推断出的输出 dtype。

这些步骤可以在 NumPy 1.26.4 源码中核对。

对于题中的数值数组:

a = np.array([np.uint8(1)])

第一步取出的 a.flat[0] 是 np.uint8 标量。第二步转换为 object 数组时,uint8 数值被装箱为 Python int,后续调用得到的就是这个 Python 对象。

普通数值数组按 dtype 存储数值,不会保存构造数组时传入的各个 np.uint8 对象。原数组的 dtype 仍然是 uint8,变化的是传给回调函数的标量表示。

为什么列表和标量输入能保留 np.uint8?

列表 [np.uint8(1)] 中已经有一个 np.uint8 对象。直接指定 dtype=object 构造数组时,可以将该对象放入数组;直接传入 np.uint8(1) 时,也会形成保存该对象的零维 object 数组。

下面的代码可以单独检查这个转换过程,无需通过 np.vectorize 观察:

import numpy as np

inputs = (
    np.array([np.uint8(1)]),
    [np.uint8(1)],
    np.uint8(1),
)

for value in inputs:
    obj = np.asanyarray(value, dtype=object)
    print(type(obj.flat[0]))

对应题中的现象,三种情况的元素类型依次为 int、numpy.uint8、numpy.uint8。

为什么结果数组仍是 uint8?

回调参数的 Python 类型和最终结果数组的 dtype 是分别处理的。

在题中对应的实现里,第一次调用返回 np.uint8(1),输出 dtype 因此被推断为 uint8。即使正式循环返回 Python int,收集后的结果仍会转换为这个输出 dtype。

因此,res.dtype == np.uint8 不能证明每次调用 f 时传入的都是 np.uint8。otypes 也只指定输出类型。NumPy 文档说明了首元素推断和 otypes 的用途。

如何让函数始终处理 np.uint8?

如果函数确实要求 np.uint8 参数,可以在调用边界显式转换,同时声明输出类型:

a = np.array([np.uint8(1)])

def f_uint8(x):
    return f(np.uint8(x))

res = np.vectorize(f_uint8, otypes=[np.uint8])(a)

这里输入本来就是 uint8 数组,转换范围明确。如果用于其他来源的数据,需要先确认数值适合转换为 uint8。

对于题中的一维数组,也可以直接迭代。数组迭代会取出 NumPy 标量,调用过程更直观:

res = np.array([f(x) for x in a], dtype=np.uint8)

单独设置 otypes=[np.uint8] 可以省掉类型推断调用,但不会约束传入 f 的参数类型。对数值数组执行 a.astype(object),也不会恢复构造数组时的 np.uint8 对象。

版本和缓存需要注意什么?

原始问题没有提供 NumPy 版本。NumPy 2.3.0 的源码已将 object 数组转换移到类型推断之前。按该实现,题中的数值数组在推断阶段也会传入 int,输出 dtype 的推断可能随之变化。排查时应先查看 np.__version__。NumPy 2.3.0 源码

cache=True 会复用首次推断调用的结果,避免首元素重复执行。对单元素输入,这可能让类型差异不再出现在打印中,但它不会统一其余元素的参数类型。

np.vectorize 主要用于方便地逐元素调用 Python 函数,通常没有性能优势。如果函数依赖参数类型,显式转换或直接迭代更容易控制行为。NumPy 文档

备注:内容仅供参考。