标准数组子类#

备注

继承 numpy.ndarray 是可能的,但如果你的目标是创建一个具有*修改*行为的数组,如同 dask 数组用于分布式计算和 cupy 数组用于基于 GPU 的计算那样,则不推荐使用继承.相反,推荐使用 numpy 的 调度机制.

如果需要,可以继承 :class:`ndarray`(在 Python 或 C 中).因此,它可以为许多有用的类奠定基础.通常,是否要子类化数组对象,或者只是将核心数组组件用作新类的内部部分,这是一个艰难的决定,而且可能只是一个选择问题.NumPy 有几个工具可以简化新对象与其他数组对象的交互方式,因此最终选择可能并不重要.简化问题的一种方法是问自己,你所关注的对象是否可以作为一个单一数组替换,或者它是否真的需要在其核心使用两个或更多数组.

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

特殊属性和方法#

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

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

在 1.13 版本加入.

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

  • ufunc 是被调用的 ufunc 对象.

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

  • inputsufunc 输入参数的元组.

  • kwargs 是一个包含 ufunc 可选输入参数的字典.如果给出,任何 out 参数,无论是位置参数还是关键字参数,都会作为 元组 传递到 kwargs 中.详见 通用函数 (ufunc) 中的讨论.

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

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

备注

我们打算将 numpy 函数重新实现为(广义的)Ufunc,在这种情况下,它们可以通过 __array_ufunc__ 方法被重写.一个主要的候选者是 matmul,它目前不是一个 Ufunc,但可以相对容易地被重写为(一组)广义 Ufuncs.类似的情况可能发生在函数如 medianaminargsort 上.

与其他一些特殊的Python方法类似,例如 __hash____iter__,可以通过设置 __array_ufunc__ = None 来表明你的类不支持ufuncs.当在设置 __array_ufunc__ = None 的对象上调用ufuncs时,总是会引发 TypeError.

存在 __array_ufunc__ 也会影响 ndarray 处理像 arr + objarr < obj 这样的二元操作,当 arr 是一个 ndarrayobj 是自定义类的一个实例时.有两种可能性.如果 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 的语法来表示数组具有”米”单位,但不希望通过 ufuncs 或其他方式与数组交互.这可以通过设置 __array_ufunc__ = None 并定义 __mul____rmul__ 方法来实现.(注意,这意味着编写一个总是返回 NotImplemented__array_ufunc__ 与设置 __array_ufunc__ = None 不完全相同:在前一种情况下,``arr + obj`` 会引发 TypeError,而在后一种情况下,可以定义一个 __radd__ 方法来防止这种情况.)

上述内容不适用于就地操作符,对于这些操作符,:class:ndarray 永远不会返回 NotImplemented.因此,``arr += obj`` 总是会导致 TypeError.这是因为对于数组来说,就地操作不能简单地被反向操作替代.(例如,默认情况下,``arr += obj`` 会被翻译为 arr = arr + obj,即 arr 会被替换,这与就地数组操作的预期相反.)

备注

如果你定义了 __array_ufunc__:

  • 如果你不是 ndarray 的子类,我们建议你的类定义像 __add____lt__ 这样的特殊方法,这些方法委托给 ufuncs,就像 ndarray 那样.一个简单的方法是继承自 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)#

在 1.16 版本加入.

  • 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 的 当前行为 略有不同.

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

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

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

__array_ufunc__ 的当前行为的一个偏差是 NumPy 只会对每种唯一类型的 第一个 参数调用 __array_function__.这符合 Python 的 调用反射方法的规则,并确保即使存在大量重载参数,检查重载也具有可接受的性能.

class.__array_finalize__(obj)#

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

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

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

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

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

备注

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

class.__array_priority__#

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

备注

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

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

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

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

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

请参阅 与 NumPy 的互操作性 以了解协议层次结构,其中 __array__ 是最旧且最不理想的.

备注

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

Matrix 对象#

备注

强烈建议 不要 使用矩阵子类.如下面所述,这使得编写能够一致处理矩阵和常规数组的函数变得非常困难.目前,它们主要用于与 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 对象.

警告

矩阵对象重载了乘法,’*’,和幂,’**’,分别为矩阵乘法和矩阵幂.如果你的子程序可以接受子类并且你不转换为基类数组,那么你必须使用ufuncs multiply和power以确保你对所有输入执行正确的操作.

矩阵类是 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.container 类是一个 Python 类,其 self.array 属性是一个 ndarray.使用 numpy.lib.user_array.container 可能比直接使用 ndarray 本身更容易实现多重继承,因此它默认包含在内.这里不再进一步介绍它的文档,因为鼓励您在可能的情况下直接使用 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

一个在数组上的一维迭代器.

如前所述,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

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

广播的一般概念也可以通过使用 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))