标准数组子类#

备注

可以对 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 的 ufuncs 的行为. 这与 Python 的 __mul__ 和其他二进制操作例程非常相似.

  • ufunc 是被调用的 ufunc 对象.

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

  • inputs 是 ufunc 的输入参数的元组.

  • 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 来表明你的类不支持 ufuncs.当在设置了 __array_ufunc__ = None 的对象上调用 Ufuncs 时,Ufuncs 总是会引发 TypeError .

__array_ufunc__ 的存在还会影响当 arrndarray 并且 obj 是一个自定义类的实例时, ndarray 如何处理像 arr + objarr < 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 代表 “meters” 单位,并且想支持语法 arr * m 来表示数组的单位是 “meters”,但不希望通过 ufuncs 或其他方式与数组进行交互.可以通过设置 __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 一样委托给 ufuncs.一种简单的方法是从 NDArrayOperatorsMixin 继承.

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

  • 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) 实现的结果.否则,它应该返回 sentinel 值 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的属性(例如,以确保2-d矩阵),或更新来自"父"的元信息.子类继承此方法的默认实现,该方法不执行任何操作.

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.

备注

对于ufuncs,希望最终能够弃用此方法,转而支持 __array_ufunc__ .

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

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

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

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

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

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

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

备注

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

矩阵对象#

备注

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

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

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

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

  3. 矩阵对象重写乘法以进行矩阵乘法.请确保您了解这一点,以便用于您可能希望接收矩阵的函数.特别是考虑到当 m 是一个矩阵时,asanyarray(m) 会返回一个矩阵.

  4. 矩阵对象重写幂运算以将矩阵提高到幂.对于在此函数内部使用幂的警告,该函数使用 asanyarray(…) 获取数组对象,这同样适用于此事实.

  5. 矩阵对象的默认 __array_priority__ 为 10.0,因此与 ndarrays 的混合运算始终产生矩阵.

  6. 矩阵具有特殊的属性,可以使计算更容易.这些是

    matrix.T 

    返回矩阵的转置.

    matrix.H 

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

    matrix.I 

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

    matrix.A 

    self 作为 ndarray 对象返回.

警告

矩阵对象会重写乘法"",和求幂"",分别为矩阵乘法和矩阵求幂. 如果您的子例程可以接受子类但您不转换为基类数组,则必须使用ufunc 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 开始,如果需要字符串数组,建议使用 dtype object_ , 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))