通用函数 ( ufunc) 基础知识#

也可以看看

通用函数 (ufunc)

通用函数(或简称ufuncndarrays )是以逐个元素的方式进行操作的函数,支持数组广播类型转换和其他几个标准功能。也就是说,ufunc 是一个函数的“向量化”包装器,它接受固定数量的特定输入并产生固定数量的特定输出。

在 NumPy 中,通用函数是 numpy.ufunc类的实例。许多内置函数都是在编译的 C 代码中实现的。基本的 ufunc 在标量上运行,但还有一种广义类型,其基本元素是子数组(向量、矩阵等),并且在其他维度上进行广播。最简单的例子是加法运算符:

>>> np.array([0,2,3,4]) + np.array([1,1,-1,2])
array([1, 3, 2, 6])

人们还可以numpy.ufunc使用工厂函数生成自定义实例 numpy.frompyfunc

Ufunc 方法#

所有 ufunc 有四种方法。它们可以在 方法中找到。然而,这些方法仅对采用两个输入参数并返回一个输出参数的标量 ufunc 有意义。尝试在其他 ufunc 上调用这些方法将导致 ValueError.

类似归约的方法都采用axis关键字、dtype 关键字和out关键字,并且数组的维度都必须 >= 1。axis关键字指定将在其上进行归约的数组的轴(带有负数)值向后计数)。一般来说,它是一个整数,但对于numpy.ufunc.reduce,它也可以是一个元组, int以一次在多个轴上减少,或者None,以在所有轴上减少。例如:

>>> x = np.arange(9).reshape(3,3)
>>> x
array([[0, 1, 2],
      [3, 4, 5],
      [6, 7, 8]])
>>> np.add.reduce(x, 1)
array([ 3, 12, 21])
>>> np.add.reduce(x, (0, 1))
36

dtype关键字允许您管理在天真的使用ufunc.reduce.有时,您可能有一个特定数据类型的数组,并希望将其所有元素相加,但结果不适合该数组的数据类型。如果您有一个单字节整数数组,则通常会发生这种情况。dtype关键字允许您更改进行归约的数据类型(以及输出的类型)。因此,您可以确保输出的数据类型的精度足以处理您的输出。改变reduce类型的责任主要取决于你。有一个例外:如果没有为“加”或“乘”运算的约简给出dtype,那么如果输入类型是整数(或布尔)数据类型并且小于 numpy.int_数据类型的大小,则将在内部向上转换为int_ (或numpy.uint) 数据类型。在前面的例子中:

>>> x.dtype
dtype('int64')
>>> np.multiply.reduce(x, dtype=float)
array([ 0., 28., 80.])

最后,out关键字允许您提供一个输出数组(对于单输出 ufunc,这是目前唯一受支持的数组;但是,为了将来的扩展,可以传入具有单个参数的元组)。如果给出了out,则忽略dtypex参数。从前面的例子来看:

>>> y = np.zeros(3, dtype=int)
>>> y
array([0, 0, 0])
>>> np.multiply.reduce(x, dtype=float, out=y)
array([ 0, 28, 80])

Ufunc 还有第五种方法,numpy.ufunc.at它允许使用高级索引执行就地操作。使用高级索引的维度不 使用缓冲,因此高级索引可以多次列出某个项目,并且该操作将对该项目的前一个操作的结果执行。

输出类型确定#

ndarray如果所有输入参数都不是 ,则 ufunc (及其方法)的输出不一定是 ndarrays。事实上,如果任何输入定义了一个 __array_ufunc__方法,控制权将完全传递给该函数,即 ufunc 被 重写

如果没有任何输入覆盖 ufunc,则所有输出数组都将传递到 定义它的输入(除了 和 标量之外)的__array_prepare__和 方法,并且具有 通用函数的任何其他输入中最高的输入。 ndarray的默认值 是0.0,子类型的默认值是0.0。矩阵等于 10.0。__array_wrap__ndarrays__array_priority____array_priority____array_priority____array_priority__

所有 ufunc 也可以接受输出参数。如有必要,输出将转换为提供的输出数组的数据类型。如果使用带有方法的类__array__作为输出,结果将写入由 . 返回的对象__array__。然后,如果该类还有一个__array_prepare__方法,则调用该方法,以便可以根据 ufunc 的上下文(由 ufunc 本身、传递给 ufunc 的参数和 ufunc 域组成的上下文)确定元数据。) 数组对象返回的 __array_prepare__被传递给ufunc进行计算。最后,如果该类还有一个__array_wrap__方法,则返回的ndarray结果将在将控制权传递回调用者之前传递给该方法。

广播#

也可以看看

广播基础知识

每个通用函数都采用数组输入,并通过对输入按元素执行核心函数来生成数组输出(其中元素通常是标量,但对于广义 ufunc 可以是向量或高阶子数组)。应用标准 广播规则,以便仍然可以有效地对不共享完全相同形状的输入进行操作。

根据这些规则,如果输入的形状维度大小为 1,则该维度中的第一个数据条目将用于沿该维度的所有计算。换句话说, ufunc的步进机制不会沿着该维度步进( 该维度的步长将为 0)。

类型转换规则#

笔记

在 NumPy 1.6.0 中,创建了类型提升 API 来封装确定输出类型的机制。有关详细信息,请参阅函数 numpy.result_typenumpy.promote_types、 和 。numpy.min_scalar_type

每个 ufunc 的核心是一个一维跨步循环,它实现特定类型组合的实际函数。创建 ufunc 时,会为其提供内部循环的静态列表以及 ufunc 操作所用的相应类型签名列表。 ufunc 机制使用此列表来确定针对特定情况使用哪个内部循环。您可以检查.types特定 ufunc 的属性,以查看哪些类型组合具有定义的内部循环以及它们生成哪种输出类型(为了简洁起见,在所述输出中使用了字符代码)。

只要 ufunc 没有针对所提供的输入类型的核心循环实现,就必须对一个或多个输入进行转换。如果找不到输入类型的实现,则算法会搜索具有类型签名的实现,所有输入都可以“安全”地转换为该类型签名。在所有必要的类型转换之后,选择并执行它在内部循环列表中找到的第一个循环。回想一下,ufunc 期间的内部副本(即使是转换)仅限于内部缓冲区的大小(用户可设置)。

笔记

NumPy 中的通用函数足够灵活,可以具有混合类型签名。因此,例如,可以定义一个适用于浮点和整数值的通用函数。请参阅 numpy.ldexp示例。

通过上面的描述,转换规则本质上是通过何时可以将一种数据类型“安全地”转换为另一种数据类型的问题来实现的。这个问题的答案可以在 Python 中通过函数调用来确定:。下面的示例显示了在作者的 64 位系统上调用 24 种内部支持的类型的结果。您可以使用示例中给出的代码为您的系统生成此表。can_cast(fromtype, totype)

例子

显示 64 位系统的“可以安全转换”表的代码段。一般来说,输出取决于系统;您的系统可能会产生不同的表。

>>> mark = {False: ' -', True: ' Y'}
>>> def print_table(ntypes):
...     print('X ' + ' '.join(ntypes))
...     for row in ntypes:
...         print(row, end='')
...         for col in ntypes:
...             print(mark[np.can_cast(row, col)], end='')
...         print()
...
>>> print_table(np.typecodes['All'])
X ? b h i l q p B H I L Q P e f d g F D G S U V O M m
? Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y - Y
b - Y Y Y Y Y Y - - - - - - Y Y Y Y Y Y Y Y Y Y Y - Y
h - - Y Y Y Y Y - - - - - - - Y Y Y Y Y Y Y Y Y Y - Y
i - - - Y Y Y Y - - - - - - - - Y Y - Y Y Y Y Y Y - Y
l - - - - Y Y Y - - - - - - - - Y Y - Y Y Y Y Y Y - Y
q - - - - Y Y Y - - - - - - - - Y Y - Y Y Y Y Y Y - Y
p - - - - Y Y Y - - - - - - - - Y Y - Y Y Y Y Y Y - Y
B - - Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y - Y
H - - - Y Y Y Y - Y Y Y Y Y - Y Y Y Y Y Y Y Y Y Y - Y
I - - - - Y Y Y - - Y Y Y Y - - Y Y - Y Y Y Y Y Y - Y
L - - - - - - - - - - Y Y Y - - Y Y - Y Y Y Y Y Y - -
Q - - - - - - - - - - Y Y Y - - Y Y - Y Y Y Y Y Y - -
P - - - - - - - - - - Y Y Y - - Y Y - Y Y Y Y Y Y - -
e - - - - - - - - - - - - - Y Y Y Y Y Y Y Y Y Y Y - -
f - - - - - - - - - - - - - - Y Y Y Y Y Y Y Y Y Y - -
d - - - - - - - - - - - - - - - Y Y - Y Y Y Y Y Y - -
g - - - - - - - - - - - - - - - - Y - - Y Y Y Y Y - -
F - - - - - - - - - - - - - - - - - Y Y Y Y Y Y Y - -
D - - - - - - - - - - - - - - - - - - Y Y Y Y Y Y - -
G - - - - - - - - - - - - - - - - - - - Y Y Y Y Y - -
S - - - - - - - - - - - - - - - - - - - - Y Y Y Y - -
U - - - - - - - - - - - - - - - - - - - - - Y Y Y - -
V - - - - - - - - - - - - - - - - - - - - - - Y Y - -
O - - - - - - - - - - - - - - - - - - - - - - - Y - -
M - - - - - - - - - - - - - - - - - - - - - - Y Y Y -
m - - - - - - - - - - - - - - - - - - - - - - Y Y - Y

您应该注意,虽然为了完整性而将其包含在表中,但 ufunc 不能对“S”、“U”和“V”类型进行操作。另请注意,在 32 位系统上,整数类型可能具有不同的大小,从而导致表略有变化。

混合标量数组操作使用一组不同的转换规则,确保标量无法“向上转换”数组,除非标量与数组的数据类型根本不同(即,在数据类型层次结构中的不同层次结构下)。大批。此规则使您能够在代码中使用标量常量(作为 Python 类型,在 ufunc 中进行相应解释),而不必担心标量常量的精度是否会导致大型(小精度)数组上的向上转换。

使用内部缓冲区#

在内部,缓冲区用于未对齐的数据、交换的数据以及必须从一种数据类型转换为另一种数据类型的数据。内部缓冲区的大小可以基于每个线程进行设置。最多可以有\(2 (n_{\mathrm{inputs}} + n_{\mathrm{outputs}})\) 创建指定大小的缓冲区来处理来自 ufunc 的所有输入和输出的数据。缓冲区的默认大小为 10,000 个元素。每当需要基于缓冲区的计算,但所有输入数组都小于缓冲区大小时,那些行为不当或类型错误的数组将在计算继续之前被复制。因此,调整缓冲区的大小可能会改变完成各种 ufunc 计算的速度。使用函数可以访问用于设置此变量的简单界面numpy.setbufsize

错误处理#

通用函数可以触发硬件中的特殊浮点状态寄存器(例如被零除)。如果您的平台上可用,这些寄存器将在计算过程中定期检查。错误处理是基于每个线程进行控制的,并且可以使用函数numpy.seterr和 进行配置numpy.seterrcall

重写 ufunc 行为#

类(包括 ndarray 子类)可以通过定义某些特殊方法来覆盖 ufunc 对它们的作用方式。有关详细信息,请参阅 标准数组子类