标准数组子类#

注意

可以继承 numpy.ndarray(在 Python 或 C 中),但如果你的目标是创建一个具有*修改*行为的数组,例如 dask 数组用于分布式计算,cupy 数组用于基于 GPU 的计算,则不建议进行子类化。建议改为使用 NumPy 的调度机制

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

请注意,asarray 始终返回基类 ndarray。如果你确信你的数组对象的使用可以处理 ndarray 的任何子类,那么可以使用asanyarray 来允许子类更清晰地传播到你的子程序中。原则上,子类可以重新定义数组的任何方面,因此,根据严格的准则,asanyarray 很少有用。但是,数组对象的大多数子类不会重新定义数组对象的某些方面,例如缓冲区接口或数组的属性。但是,你的子程序可能无法处理数组的任意子类的一个重要示例是,矩阵将“*”运算符重新定义为矩阵乘法,而不是逐元素乘法。

特殊属性和方法#

另请参阅

ndarray 的子类化

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,但可以相对容易地重写为(一组)广义 Ufunc。类似的情况也可能发生在medianaminargsort 等函数上。

与 Python 中的一些其他特殊方法(例如 __hash____iter__)一样,可以通过设置 __array_ufunc__ = None 来指示你的类不支持 ufunc。当在设置 __array_ufunc__ = None 的对象上调用 ufunc 时,ufunc 始终会引发TypeError

存在 __array_ufunc__ 也会影响 ndarray 如何处理二元运算,例如当 arrndarrayobj 是自定义类的实例时,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,并希望支持语法 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 是来自原始 NumPy 函数调用的唯一参数类型的集合 collections.abc.Collection,这些类型实现了 __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的当前行为略有不同。

  • __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的调用反射方法的规则相匹配,这确保了即使有大量重载参数,检查重载也能具有可接受的性能。

类.__array_finalize__(obj)#

每当系统从obj内部分配一个新的数组时,都会调用此方法,其中objndarray的子类(子类型)。它可以用来在构造后更改self的属性(例如,确保一个二维矩阵),或者从“父”对象更新元信息。子类继承此方法的默认实现,该实现不执行任何操作。

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

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

NumPy也可能在非ufunc中无上下文调用此函数,以允许保留子类信息。

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

注意

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

类.__array_priority__#

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

注意

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

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

如果在对象上定义,则应返回一个ndarray。如果将实现此接口的对象传递给这些函数,则数组强制转换函数(如np.array())将调用此方法。 __array__ 的第三方实现必须采用 dtypecopy 关键字参数,因为忽略它们可能会破坏第三方代码或 NumPy 本身。

  • dtype是返回数组的数据类型。

  • copy是一个可选布尔值,指示是否应返回副本。对于True,应始终创建副本;对于None,仅在需要时(例如,由于传递了dtype值),以及对于False,永远不应创建副本(如果仍然需要副本,则应引发适当的异常)。

请参考与NumPy的互操作性了解协议层次结构,其中__array__是最旧且最不可取的。

注意

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

矩阵对象#

注意

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

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

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

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

  3. 矩阵对象重载乘法运算使其成为矩阵乘法。请确保您理解这一点,因为您可能希望函数接收矩阵。尤其考虑到 asanyarray(m) 在 m 为矩阵时返回矩阵这一事实。

  4. 矩阵对象重载幂运算使其成为矩阵的幂运算。关于在使用 asanyarray(…) 获取数组对象的函数内部使用幂运算的相同警告也适用于此。

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

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

    matrix.T

    返回矩阵的转置。

    matrix.H

    返回self的(复数)共轭转置。

    matrix.I

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

    matrix.A

    self作为ndarray对象返回。

警告

矩阵对象分别重载乘法运算符“*”和幂运算符“**”,使其成为矩阵乘法和矩阵幂运算。如果您的子程序可以接受子类并且您不转换为基类数组,则必须使用 ufunc 函数 multiply 和 power 来确保您对所有输入都执行了正确的运算。

matrix 类是 ndarray 的 Python 子类,可以用作构建您自己的 ndarray 子类的参考。矩阵可以从其他矩阵、字符串以及任何可以转换为ndarray 的内容创建。在 NumPy 中,“mat”是“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维迭代器。

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

>>> import numpy as np
>>> a = np.arange(24).reshape(3,2,4) + 10
>>> 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 也提供了广播的通用概念。这个对象接收\(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))