标准数组子类#
备注
可以对 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,amin和argsort等函数也可能发生同样的情况.与 python 中的其他一些特殊方法(如
__hash__和__iter__)一样,可以通过设置__array_ufunc__ = None来表明你的类不支持 ufuncs.当在设置了__array_ufunc__ = None的对象上调用 Ufuncs 时,Ufuncs 总是会引发TypeError.__array_ufunc__的存在还会影响当arr是ndarray并且obj是一个自定义类的实例时,ndarray如何处理像arr + obj和arr < 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__的实现将从两个检查开始:给定的函数是我们知道如何重载的吗?
所有参数都是我们知道如何处理的类型吗?
如果这些条件成立,
__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__的第三方实现必须接受dtype和copy参数.自 NumPy 版本弃用: 2.0 从NumPy 2版本开始,不实现
copy和dtype已被弃用. 添加它们时,必须确保copy的正确行为.dtype是返回数组的请求数据类型,由NumPy按位置传递(仅当用户请求时).可以忽略dtype,因为NumPy会检查结果并在必要时强制转换为dtype.如果更有效地将数据强制转换为请求的dtype而不依赖NumPy,则应在您的库中处理它.copy是一个通过关键字传递的布尔值.如果copy=True,则必须返回一个副本.返回现有数据的视图将导致不正确的用户代码.如果copy=False,则用户请求永远不要创建副本,并且您必须引发错误,除非未创建副本并且返回的数组是现有数据的视图.始终为copy=False引发错误是有效的.默认值copy=None(未传递)允许结果是视图或副本.但是,应尽可能首选视图返回.
请参阅 Interoperability with NumPy 以了解协议层次结构,其中
__array__是最旧且最不受欢迎的.
矩阵对象#
备注
强烈建议不要使用matrix子类.如下所述,它使得编写能够始终如一地处理矩阵和常规数组的函数非常困难.目前,它们主要用于与 scipy.sparse 进行交互.我们希望为此用途提供替代方案,并最终删除 matrix 子类.
matrix 对象继承自ndarray,因此,它们具有与ndarrays相同的属性和方法.但是,matrix对象有六个重要的区别,当您使用矩阵但期望它们像数组一样工作时,可能会导致意外的结果:
可以使用字符串表示法创建矩阵对象,以允许Matlab样式的语法,其中空格分隔列,分号(‘;’)分隔行.
矩阵对象始终是二维的.这具有深远的影响,因为 m.ravel() 仍然是二维的(第一维为 1),并且项目选择返回二维对象,因此序列行为与数组根本不同.
矩阵对象重写乘法以进行矩阵乘法.请确保您了解这一点,以便用于您可能希望接收矩阵的函数.特别是考虑到当 m 是一个矩阵时,asanyarray(m) 会返回一个矩阵.
矩阵对象重写幂运算以将矩阵提高到幂.对于在此函数内部使用幂的警告,该函数使用 asanyarray(…) 获取数组对象,这同样适用于此事实.
矩阵对象的默认 __array_priority__ 为 10.0,因此与 ndarrays 的混合运算始终产生矩阵.
矩阵具有特殊的属性,可以使计算更容易.这些是
警告
矩阵对象会重写乘法"",和求幂"",分别为矩阵乘法和矩阵求幂. 如果您的子例程可以接受子类但您不转换为基类数组,则必须使用ufunc multiply和power以确保您对所有输入执行正确的操作.
matrix 类是 ndarray 的一个 Python 子类,可以用作构建您自己的 ndarray 子类的参考.可以从其他矩阵,字符串以及可以转换为 ndarray 的任何内容创建矩阵.名称"mat"是 NumPy 中"matrix"的别名.
|
从类数组对象或数据字符串返回一个矩阵. |
|
将输入解释为矩阵. |
|
从字符串,嵌套序列或数组构建矩阵对象. |
示例 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() ,用户必须手动调用该方法以确保对数组的任何更改实际写入磁盘.
|
创建到存储在磁盘上的二进制文件中的数组的内存映射. |
|
将数组中的任何更改写入磁盘上的文件. |
示例:
>>> 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:
|
提供字符串和 unicode 值数组的方便视图. |
|
创建一个 |
与 str 数据类型的标准 ndarray 的另一个区别是,chararray 继承了 Numarray 引入的特性,即在项目检索和比较操作中,数组中任何元素末尾的空格将被忽略.
记录数组#
参见
创建记录数组 , 数据类型例程 , 数据类型对象 ( dtype ) .
NumPy 提供了 recarray 类,该类允许将结构化数组的字段作为属性访问,以及相应的标量数据类型对象 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 类.
|
用于简化多重继承的标准容器类. |
数组迭代器#
迭代器是数组处理的一个强大概念.本质上,迭代器实现了广义的 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]]
扁平迭代#
一个在数组上进行 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 维枚举#
|
多维索引迭代器. |
有时,在迭代时获取 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
用于广播的迭代器#
生成一个模仿广播的对象. |
广播的一般概念也可以通过 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))