标准数组子类#

备注

numpy.ndarray 进行子类化是可能的,但如果您的目标是创建具有修改行为的数组,例如用于分布式计算的 dask 数组和用于基于 GPU 计算的 cupy 数组,则不建议使用子类化. 相反,建议使用 numpy 的 dispatch mechanism .

如果需要,可以从 ndarray 继承(在 Python 或 C 中). 因此,它可以构成许多有用类的基础. 通常,子类化数组对象还是仅将核心数组组件用作新类的内部部分是一个困难的决定,并且可能只是一个选择问题. NumPy 具有多种工具来简化您的新对象与其他数组对象的交互方式,因此最终选择可能并不重要. 简化问题的一种方法是问问自己,您感兴趣的对象是否可以替换为单个数组,或者它是否确实需要两个或多个数组作为其核心.

请注意, asarray 始终返回基类 ndarray. 如果您确信您对数组对象的使用可以处理 ndarray 的任何子类,则可以使用 asanyarray 来允许子类更干净地通过您的子例程传播. 原则上,子类可以重新定义数组的任何方面,因此,在严格的指导原则下, asanyarray 很少有用. 但是,数组对象的大多数子类不会重新定义数组对象的某些方面,例如缓冲区接口或数组的属性. 然而,一个重要的例子是,为什么你的子程序可能无法处理数组的任意子类,那就是矩阵重新定义了 “” 运算符为矩阵乘法,而不是逐个元素的乘法.

特殊属性和方法#

NumPy 提供了几个类可以自定义的钩子:

class.__array_ufunc__(ufunc, method, *inputs, **kwargs)#

任何类,无论是 ndarray 子类还是非子类,都可以定义此方法或将其设置为 None,以覆盖 NumPy ufunc 的行为. 这与 Python 的 __mul__ 和其他二进制操作例程非常相似.

  • ufunc 是被调用的 ufunc 对象.

  • method 是一个字符串,指示调用了哪个 Ufunc 方法( "__call__" , "reduce" , "reduceat" , "accumulate" , "outer" , "inner" 之一).

  • inputsufunc 的输入参数的元组.

  • kwargs 是一个字典,其中包含 ufunc 的可选输入参数. 如果给定,任何 out 参数,无论是位置参数还是关键字参数,都将作为 tuple 传递到 kwargs 中. 有关详细信息,请参见 通用函数 ( ufunc ) 中的讨论.

如果所请求的操作未实现,则该方法应返回操作结果或 NotImplemented .

如果输入,输出或 where 参数之一具有 __array_ufunc__ 方法,则执行该方法而不是 ufunc.如果多个参数实现了 __array_ufunc__ ,则按以下顺序尝试它们:子类优先于超类,输入优先于输出,输出优先于 where ,否则从左到右.第一个返回非 NotImplemented 的例程决定结果.如果所有 __array_ufunc__ 操作都返回 NotImplemented ,则引发 TypeError .

备注

我们打算将 numpy 函数重新实现为(广义的)Ufunc,在这种情况下, __array_ufunc__ 方法可以覆盖它们.一个主要的候选对象是 matmul ,它目前不是 Ufunc,但可以相对容易地重写为(一组)广义 Ufuncs.对于诸如 median , aminargsort 之类的函数,可能会发生同样的情况.

与 Python 中的某些特殊方法(例如 __hash____iter__ )类似,可以通过设置 __array_ufunc__ = None 来表明你的类不支持 ufunc.当对设置了 __array_ufunc__ = None 的对象调用 ufunc 时,始终会引发 TypeError .

__array_ufunc__ 的存在也会影响 ndarray 如何处理二元运算,如 arr + objarr < obj ,其中 arr 是一个 ndarray , obj 是一个自定义类的实例.有两种可能性.如果 obj.__array_ufunc__ 存在且不为 None,则 ndarray.__add__ 及类似方法将委托给 ufunc 机制,这意味着 arr + obj 变为 np.add(arr, obj) ,然后 add 调用 obj.__array_ufunc__ .如果你想定义一个行为类似于数组的对象,这很有用.

或者,如果 obj.__array_ufunc__ 设置为 None,作为一个特殊情况,像 ndarray.__add__ 这样的特殊方法会注意到这一点,并无条件地引发 TypeError .如果你想创建通过二元运算与数组交互,但本身不是数组的对象,这很有用.例如,一个单位处理系统可能有一个表示 “米” 单位的对象 m ,并希望支持 arr * m 语法来表示该数组的单位为 “米”,但不希望通过 ufunc 或其他方式与数组进行交互.可以通过设置 __array_ufunc__ = None 并定义 __mul____rmul__ 方法来实现.(请注意,这意味着编写一个始终返回 NotImplemented__array_ufunc__ 与设置 __array_ufunc__ = None 并不完全相同:在前一种情况下, arr + obj 将引发 TypeError ,而在后一种情况下,可以定义一个 __radd__ 方法来防止这种情况.)

上述情况不适用于原地运算符, ndarray 永远不会为其返回 NotImplemented .因此, arr += obj 总是会导致 TypeError .这是因为对于数组,原地操作不能通过简单的反向操作来通用地替换.(例如,默认情况下, arr += obj 将被转换为 arr = arr + obj ,即 arr 将被替换,这与原地数组操作的预期相反.)

备注

如果你定义了 __array_ufunc__ :

  • 如果你不是 ndarray 的子类,我们建议你的类定义像 __add____lt__ 这样的特殊方法,这些方法像 ndarray 一样委托给 ufunc.做到这一点的一个简单方法是从 NDArrayOperatorsMixin 继承.

  • 如果你继承 ndarray ,我们建议你将所有重写逻辑放在 __array_ufunc__ 中,而不是同时重写特殊方法.这确保了类层次结构仅在一个地方确定,而不是分别由 ufunc 机制和二元运算规则确定(二元运算规则优先考虑子类的特殊方法;强制执行单点层次结构的另一种方法,即将 __array_ufunc__ 设置为 None,看起来非常意外,因此令人困惑,因为这样子类根本无法与 ufunc 一起使用).

  • ndarray 定义了自己的 __array_ufunc__ ,如果没有参数具有重写,则该函数计算 ufunc,否则返回 NotImplemented .这对于 __array_ufunc__ 将其自身类的任何实例转换为 ndarray 的子类可能很有用:然后,它可以将这些实例使用 super().__array_ufunc__(inputs, kwargs) 传递给其超类,并最终在可能的回退转换后返回结果.这种做法的优点是,它可以确保可以拥有扩展行为的子类层次结构.有关详细信息,请参见 Subclassing ndarray .

class.__array_function__(func, types, args, kwargs)#
  • func 是 NumPy 公开 API 公开的任意可调用对象,它以 func(args, kwargs) 的形式被调用.

  • types 是一个 collections.abc.Collection 集合,包含来自原始 NumPy 函数调用的唯一参数类型,这些类型实现了 __array_function__ .

  • 元组 args 和字典 kwargs 直接从原始调用传递.

为了方便 __array_function__ 的实现者, types 提供了所有具有 '__array_function__' 属性的参数类型.这允许实现者快速识别他们应该推迟到其他参数上的 __array_function__ 实现的情况.实现不应依赖于 types 的迭代顺序.

大多数 __array_function__ 的实现将从两个检查开始:

  1. 给定的函数是我们知道如何重载的函数吗?

  2. 所有参数都是我们知道如何处理的类型吗?

如果这些条件成立, __array_function__ 应该返回调用其 func(args, kwargs) 实现的结果.否则,它应该返回哨兵值 NotImplemented ,表明这些类型未实现该函数.

__array_function__ 的返回值没有一般要求,尽管大多数合理的实现可能应该返回与函数参数之一类型相同的数组.

定义自定义装饰器(下面的 implements )来注册 __array_function__ 实现也可能很方便.

HANDLED_FUNCTIONS = {}

class MyArray:
    def __array_function__(self, func, types, args, kwargs):
        if func not in HANDLED_FUNCTIONS:
            return NotImplemented
        # Note: this allows subclasses that don't override
        # __array_function__ to handle MyArray objects
        if not all(issubclass(t, MyArray) for t in types):
            return NotImplemented
        return HANDLED_FUNCTIONS[func](*args, **kwargs)

def implements(numpy_function):
    """Register an __array_function__ implementation for MyArray objects."""
    def decorator(func):
        HANDLED_FUNCTIONS[numpy_function] = func
        return func
    return decorator

@implements(np.concatenate)
def concatenate(arrays, axis=0, out=None):
    ...  # implementation of concatenate for MyArray objects

@implements(np.broadcast_to)
def broadcast_to(array, shape):
    ...  # implementation of broadcast_to for MyArray objects

请注意, __array_function__ 实现不需要包含所有对应的 NumPy 函数的可选参数(例如,上面的 broadcast_to 省略了不相关的 subok 参数).可选参数只有在 NumPy 函数调用中显式使用时才会传递给 __array_function__ .

就像内置特殊方法(如 __add__ )的情况一样,正确编写的 __array_function__ 方法在遇到未知类型时应始终返回 NotImplemented .否则,如果操作还包括你的对象之一,则无法从另一个对象正确覆盖 NumPy 函数.

在很大程度上,使用 __array_function__ 进行调度的规则与 __array_ufunc__ 的规则相匹配.特别是:

  • NumPy 将从所有指定的输入中收集 __array_function__ 的实现,并按顺序调用它们:子类优先于超类,否则从左到右.请注意,在涉及子类的某些极端情况下,这与 Python 的 current behavior 略有不同.

  • __array_function__ 的实现通过返回除 NotImplemented 之外的任何值来表明它们可以处理该操作.

  • 如果所有 __array_function__ 方法都返回 NotImplemented ,NumPy 将引发 TypeError .

如果不存在 __array_function__ 方法,NumPy 将默认调用其自己的实现,该实现旨在用于NumPy数组.例如,当所有类似数组的参数都是 Python 数字或列表时,就会出现这种情况.(NumPy 数组确实具有 __array_function__ 方法,如下所示,但是如果 NumPy 数组子类之外的任何参数实现了 __array_function__ ,它总是返回 NotImplemented .)

__array_ufunc__ 的当前行为的一个偏差是 NumPy 只会在每个唯一类型的第一个参数上调用 __array_function__ .这与 Python 的 rule for calling reflected methods 匹配,并且确保即使有大量的重载参数,检查重载也能具有可接受的性能.

class.__array_finalize__(obj)#

当系统从 obj 内部新分配数组时,将调用此方法.其中,obj 是 ndarray 的子类(子类型).它可用于在构造后更改 self 的属性(例如,以确保是二维矩阵),或更新来自“父级”的元信息.子类继承了此方法的默认实现,该实现不执行任何操作.

class.__array_wrap__(array, context=None, return_scalar=False)#

在每个 ufunc 的最后,会在具有最高数组优先级的输入对象或指定的输出对象上调用此方法. ufunc计算的数组被传入,并且返回的任何内容都会传递给用户. 子类继承此方法的默认实现,该方法将数组转换为对象类的新实例. 子类可以选择使用此方法将输出数组转换为子类的实例,并在将数组返回给用户之前更新元数据.

NumPy 也可能在没有上下文的情况下从非 ufunc 调用此函数,以允许保留子类信息.

在 2.0 版本发生变更: return_scalar 现在作为 False (通常)或 True 传递,指示 NumPy 将返回一个标量. 子类可以忽略该值,或者返回 array[()] ,使其行为更像 NumPy.

备注

希望最终弃用此方法,转而使用 ufunc 的 __array_ufunc__ (以及一些其他函数的 __array_function__ ,如 numpy.squeeze ).

class.__array_priority__#

此属性的值用于确定在返回对象的 Python 类型存在多种可能性时要返回的对象类型. 子类继承此属性的默认值 0.0.

备注

对于 ufunc,希望最终弃用此方法,转而使用 __array_ufunc__ .

class.__array__(dtype=None, copy=None)#

如果对象上定义了此方法,则它必须返回一个 NumPy ndarray . 如果将实现此接口的对象传递给数组强制转换函数(如 np.array() ),则将调用此方法.

__array__ 的第三方实现必须接受 dtypecopy 参数.

自 NumPy 版本弃用: 2.0 不实现 copydtype 已在 NumPy 2 中弃用.添加它们时,必须确保 copy 的正确行为.

  • dtype 是返回数组的请求数据类型,并由 NumPy 按位置传递(仅在用户请求时). 可以忽略 dtype ,因为 NumPy 将检查结果并在必要时强制转换为 dtype . 如果在不依赖 NumPy 的情况下将数据强制转换为请求的 dtype 更有效,则应在库中处理它.

  • copy 是一个通过关键字传递的布尔值. 如果 copy=True ,则必须返回一个副本. 返回现有数据的视图将导致不正确的用户代码. 如果 copy=False ,则用户请求永不创建副本,并且除非不创建副本且返回的数组是现有数据的视图,否则必须引发错误. 始终为 copy=False 引发错误是有效的. 默认值 copy=None (未传递)允许结果是视图或副本. 但是,应尽可能优先返回视图.

请参阅 Interoperability with NumPy 以获取协议层次结构,其中 __array__ 是最旧且最不受欢迎的.

备注

如果将具有 __array__ 方法的类(ndarray 子类或非子类)用作 ufunc 的输出对象,则结果不会写入 __array__ 返回的对象. 这种做法将返回 TypeError .

矩阵对象#

备注

强烈建议不要使用矩阵子类. 如下所述,它使得编写能够一致地处理矩阵和常规数组的函数非常困难. 目前,它们主要用于与 scipy.sparse 交互. 我们希望为此用途提供替代方案,并最终删除 matrix 子类.

matrix 对象继承自 ndarray,因此,它们具有与 ndarray 相同的属性和方法. 但是,矩阵对象有六个重要的差异,当您使用矩阵但希望它们像数组一样工作时,可能会导致意外的结果:

  1. 可以使用字符串表示法创建矩阵对象,以允许使用 Matlab 样式的语法,其中空格分隔列,分号 (‘;’) 分隔行.

  2. 矩阵对象始终是二维的.这具有深远的影响,因为 m.ravel() 仍然是二维的(第一维为 1),并且项目选择返回二维对象,因此序列行为与数组从根本上不同.

  3. 矩阵对象重写乘法以进行矩阵乘法.请确保您了解这一点,以便用于您可能希望接收矩阵的函数.尤其是在 asanyarray(m) 在 m 是矩阵时返回矩阵的情况下.

  4. 矩阵对象重写幂运算,使其成为矩阵的幂运算.关于在函数内部使用 power 的相同警告(该函数使用 asanyarray(…) 获取数组对象)也适用于这一事实.

  5. 矩阵对象的默认 __array_priority__ 为 10.0,因此与 ndarray 的混合运算总是产生矩阵.

  6. 矩阵具有特殊的属性,可以简化计算.这些是

    matrix.T 

    返回矩阵的转置.

    matrix.H 

    返回 self 的(复共轭)转置.

    matrix.I 

    返回可逆 self 的(乘法)逆矩阵.

    matrix.A 

    self 作为 ndarray 对象返回.

警告

矩阵对象分别重写乘法 ‘’和幂 ‘’,使其成为矩阵乘法和矩阵幂.如果您的子例程可以接受子类,并且您不转换为基类数组,则必须使用 ufuncs multiply 和 power 以确保对所有输入执行正确的操作.

matrix 类是 ndarray 的 Python 子类,可以用作如何构建自己的 ndarray 子类的参考. 可以从其他矩阵,字符串以及可以转换为 ndarray 的任何其他内容创建矩阵. 名称“mat”是 NumPy 中“matrix”的别名.

matrix (data[, dtype, copy])

从类数组对象或数据字符串返回矩阵.

asmatrix (data[, dtype])

将输入解释为矩阵.

bmat (obj[, ldict, gdict])

从字符串,嵌套序列或数组构建矩阵对象.

示例 1:从字符串创建矩阵

>>> import numpy as np
>>> a = np.asmatrix('1 2 3; 4 5 3')
>>> print((a*a.T).I)
  [[ 0.29239766 -0.13450292]
  [-0.13450292  0.08187135]]

示例 2:从嵌套序列创建矩阵

>>> import numpy as np
>>> np.asmatrix([[1,5,10],[1.0,3,4j]])
matrix([[  1.+0.j,   5.+0.j,  10.+0.j],
        [  1.+0.j,   3.+0.j,   0.+4.j]])

示例 3:从数组创建矩阵

>>> import numpy as np
>>> np.asmatrix(np.random.rand(3,3)).T
matrix([[4.17022005e-01, 3.02332573e-01, 1.86260211e-01],
        [7.20324493e-01, 1.46755891e-01, 3.45560727e-01],
        [1.14374817e-04, 9.23385948e-02, 3.96767474e-01]])

内存映射文件数组#

内存映射文件对于读取和/或修改具有规则布局的大型文件的小段非常有用,而无需将整个文件读取到内存中. ndarray 的一个简单子类使用内存映射文件作为数组的数据缓冲区. 对于小文件,将整个文件读取到内存中的开销通常并不显着,但是对于大文件,使用内存映射可以节省大量资源.

内存映射文件数组有一个附加方法(除了它们从 ndarray 继承的方法之外) .flush() ,用户必须手动调用它以确保对数组的任何更改实际上都被写入磁盘.

memmap (filename[, dtype, mode, offset, ...])

创建到存储在磁盘上的二进制文件中的数组的内存映射.

memmap.flush ()

将数组中的任何更改写入磁盘上的文件.

示例:

>>> import numpy as np
>>> a = np.memmap('newfile.dat', dtype=float, mode='w+', shape=1000)
>>> a[10] = 10.0
>>> a[30] = 30.0
>>> del a
>>> b = np.fromfile('newfile.dat', dtype=float)
>>> print(b[10], b[30])
10.0 30.0
>>> a = np.memmap('newfile.dat', dtype=float)
>>> print(a[10], a[30])
10.0 30.0

字符数组 ( numpy.char )#

备注

chararray 类是为了向后兼容 Numarray 而存在的,不建议用于新的开发.从 numpy 1.4 开始,如果需要字符串数组,建议使用 dtypeobject_ , bytes_str_ 的数组,并使用 numpy.char 模块中的自由函数进行快速矢量化字符串操作.

这些是 str_ 类型或 bytes_ 类型的增强数组.这些数组继承自 ndarray ,但专门定义了在(广播)逐个元素的基础上的 + , *% 操作.这些操作在字符类型的标准 ndarray 上不可用.此外, chararray 具有所有标准的 str (和 bytes )方法,并在逐个元素的基础上执行它们.创建 chararray 最简单的方法可能是使用 self.view(chararray) ,其中 self 是 str 或 unicode 数据类型的 ndarray.但是,也可以使用 chararray 构造函数或通过 numpy.char.array 函数创建 chararray:

char.chararray (shape[, itemsize, unicode, ...])

提供字符串和 unicode 值数组的便捷视图.

char.array (obj[, itemsize, copy, unicode, order])

创建 chararray .

与 str 数据类型的标准 ndarray 的另一个区别是,chararray 继承了 Numarray 引入的特性,即在项检索和比较操作中,数组中任何元素末尾的空格将被忽略.

记录数组#

NumPy 提供了 recarray 类,允许将结构化数组的字段作为属性访问,以及相应的标量数据类型对象 record .

recarray (shape[, dtype, buf, offset, ...])

构造一个允许使用属性进行字段访问的 ndarray.

record 

一种数据类型标量,允许将字段访问作为属性查找.

备注

pandas DataFrame 比记录数组更强大.如果可能,请使用 pandas DataFrame 代替.

屏蔽数组 ( numpy.ma )#

参见

掩码数组

标准容器类#

为了向后兼容,并且作为标准的“容器”类,Numeric 中的 UserArray 已被引入到 NumPy 中,并命名为 numpy.lib.user_array.container .容器类是一个 Python 类,其 self.array 属性是一个 ndarray.与 ndarray 本身相比,使用 numpy.lib.user_array.container 进行多重继承可能更容易,因此默认情况下包含它.这里没有对其进行详细说明,只是提到了它的存在,因为我们鼓励您在可能的情况下直接使用 ndarray 类.

numpy.lib.user_array.container (data[, ...])

用于简化多重继承的标准容器类.

数组迭代器#

迭代器是数组处理的一个强大概念.本质上,迭代器实现了一个广义的 for 循环.如果 myiter 是一个迭代器对象,那么 Python 代码:

for val in myiter:
    ...
    some code involving val
    ...

重复调用 val = next(myiter) 直到迭代器引发 StopIteration .有几种迭代数组的方式可能很有用:默认迭代,扁平迭代和 \(N\) 维枚举.

默认迭代#

ndarray 对象的默认迭代器是序列类型的默认 Python 迭代器.因此,当数组对象本身用作迭代器时.默认行为等效于:

for i in range(arr.shape[0]):
    val = arr[i]

这个默认迭代器从数组中选择一个维度为 \(N-1\) 的子数组.这对于定义递归算法可能很有用.循环遍历整个数组需要 \(N\) 个 for 循环.

>>> import numpy as np
>>> a = np.arange(24).reshape(3,2,4) + 10
>>> for val in a:
...     print('item:', val)
item: [[10 11 12 13]
[14 15 16 17]]
item: [[18 19 20 21]
[22 23 24 25]]
item: [[26 27 28 29]
[30 31 32 33]]

扁平迭代#

ndarray.flat 

数组上的 1-D 迭代器.

如前所述,ndarray 对象的 flat 属性返回一个迭代器,它将以 C 风格的连续顺序循环遍历整个数组.

>>> import numpy as np
>>> for i, val in enumerate(a.flat):
...     if i%5 == 0: print(i, val)
0 10
5 15
10 20
15 25
20 30

这里,我使用了内置的 enumerate 迭代器来返回迭代器索引以及值.

N 维枚举#

ndenumerate (arr)

多维索引迭代器.

有时,在迭代时获取 N 维索引可能很有用. ndenumerate 迭代器可以实现这一点.

>>> import numpy as np
>>> for i, val in np.ndenumerate(a):
...     if sum(i)%5 == 0:
            print(i, val)
(0, 0, 0) 10
(1, 1, 3) 25
(2, 0, 3) 29
(2, 1, 2) 32

广播的迭代器#

broadcast 

生成一个模仿广播的对象.

广播的一般概念也可以从 Python 中使用 broadcast 迭代器获得.此对象将 \(N\) 个对象作为输入,并返回一个迭代器,该迭代器返回元组,这些元组提供广播结果中每个输入序列元素.

>>> import numpy as np
>>> for val in np.broadcast([[1, 0], [2, 3]], [0, 1]):
...     print(val)
(np.int64(1), np.int64(0))
(np.int64(0), np.int64(1))
(np.int64(2), np.int64(0))
(np.int64(3), np.int64(1))