标准数组子类
注意
可以对 numpy.ndarray
进行子类化, 但如果您的目标是创建具有 修改 的行为的数组, 就像用于分布式计算的 Dask 数组和用于基于 GPU 的计算的 cupy 数组一样,则不鼓励子类化。 相反,建议使用 numpy 的调度机制。
如果需要,可以从( Python 或 C )继承 ndarray
。 因此,它可以形成许多有用的类的基础。 通常是对数组对象进行子类,还是简单地将核心数组件用作新类的内部部分,这是一个困难的决定,可能只是一个选择的问题。 NumPy 有几个工具可以简化新对象与其他数组对象的交互方式,因此最终选择可能并不重要。 简化问题的一种方法是问问自己,您感兴趣的对象是否可以替换为单个数组,或者它的核心是否真的需要两个或更多个数组。
注意,asarray
总是返回基类 ndarray。 如果您确信使用数组对象可以处理 ndarray 的任何子类, 那么可以使用 asanyarray
来允许子类通过您的子例程更干净地传播。 原则上,子类可以重新定义数组的任何方面,因此,在严格的指导原则下,asanyarray
很少有用。 然而,数组对象的大多数子类不会重新定义数组对象的某些方面,例如 Buffer 接口或数组的属性。 但是,子例程可能无法处理数组的任意子类的一个重要示例是,矩阵将 “*” 运算符重新定义为矩阵乘法,而不是逐个元素的乘法。
特殊属性和方法
NumPy 提供了几个类可以自定义的钩子:
class.__array_ufunc__
(ufunc, method, *inputs, ***kwargs*)版本 1.13 中的新功能。
任何类,ndarray 子类是否可以定义此方法或将其设置为
None
以覆盖 NumPy 的 ufuncs 的行为。 这与 Python 的mul和其他二进制操作例程非常相似。- ufunc 是被调用的 ufunc 对象。
- method 是一个字符串,指示调用了哪个 Ufunc 方法(
"__call__"
,"reduce"
,"acculate"
,"outer"
,"internal"
之一)。 - inputs 是
ufunc
的输入参数的元组。 - kwargs 是包含 ufunc 的可选输入参数的字典。 如果给定,任何
out
参数(包括位置参数和关键字)都将作为 kwargs 中的元组
传递。有关详细信息,请参阅 通函数(ufunc) 中的讨论。
该方法应返回操作的结果,如果未实现请求的操作,则返回
NotImplemented
。如果输入或输出参数之一具有
__array_ufunc__
方法,则执行该方法而不是 ufunc。 如果多个参数实现__array_ufunc__
,则按顺序尝试它们:子类在超类之前,输入在输出之前,否则从左到右。 返回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 总是引发TypeError
。当 arr 是
ndarray
且obj
是自定义类的实例时,__array_ufunc__
的存在也会影响ndarray
处理arr + obj
和arr < obj
等二进制操作的方式。 有两种可能性。如果obj.__array_ufunc__
存在而不是 None, 则ndarray.__add__
和 friends 将委托给 ufunc 机器, 这意味着arr + obj
变为np.add(arr,obj)
,然后 add 调用obj.__array_ufunc__
。 如果您想定义一个像数组一样作用的对象,这是很有用的。或者,如果
obj.__array_ufunc__
设置为None
, 那么作为一种特殊情况,像ndarray.__add__
这样的特殊方法会注意到这一点,并 无条件 地引发TypeError
。 如果要创建通过二进制操作与数组交互的对象,但本身不是数组,则这很有用。 例如,单位处理系统可能具有表示 “meters” 单位的对象m
,并且希望支持语法arr * m
来表示数组具有 “meters” 单位, 但不想通过 ufuncs 或其他方式与数组交互。 这可以通过设置__array_ufunc_ = none
并定义__mul_
和__rmul__
方法来完成。 (请注意,这意味着编写始终返回NotImplemented
的__array_ufunc__
与设置__array_ufunc__ = none
并不完全相同: 在前一种情况下,arr + obj
将引发TypeError
,而在后一种情况下,可以定义__radd__
方法来防止这种情况。)以上不适用于 in-place 操作符,
ndarray
从不返回NotImplemented
。因此,arr += obj
总是会导致TypeError
。这是因为对于数组来说,in-place 操作一般不能被简单的反向操作所取代。(例如,默认情况下,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)
将这些实例传递给其超类,并最终在可能的反向转换后返回结果。这种做法的优点是,它确保了有可能具有扩展行为的子类的层次结构。 有关详细信息,请参见 子类化 ndarray。
注意
如果类定义了
__array_ufunc__
方法,这将禁用下面为 ufuncs 描述的__array_wrap__
、__array_prepare__
、__array_priority__
机制(最终可能会被弃用)。class.__array_function__
(func, types, args, kwargs)版本 1.16 中的新增功能。
注意
- 在 NumPy 1.17 中,默认情况下启用该协议,但可以禁用该协议,设置
NUMPY_EXPERIMENTAL_ARRAY_FUNCTION = 0
。 - 在 NumPy 1.16 中,您需要设置环境变量
- 在 NumPy 1.16 中,您需要在导入 NumPy 之前设置环境变量
NUMPY_EXPERIMENTAL_ARRAY_FUNCTION = 1
以使用 NumPy 函数覆盖。 - 最终,期望始终启用
__array_function__
。
func
是由 NumPy 的公共 API 公开的任意可调用的,它以func(*args, **kwargs)
的形式调用。types
是来自实现 array_function 的原始 NumPy 函数调用的唯一参数类型集合
。- 元组
args
和 dictkwargs
直接从原始调用传递。
为了方便
__array_function__
实现者,types
为所有参数类型提供了'__array_function__'
属性。 这允许实现者快速识别他们应该遵循其他参数的__array_function__
实现的情况。 实现不应该依赖于类型
的迭代顺序。__array_function__
的大多数实现都将从两个检查开始:- 给定的函数是否知道如何重载?
- 我们知道如何处理的类型的所有参数都是?
如果这些条件成立,
__array_function__
应该返回调用func(*args, **kwargs)
实现的结果。 否则,它应该返回 sentinel 值NotImplemented
,表示该函数未由这些类型实现。对
__array_function__
的返回值没有一般要求,尽管大多数合理的实现应该返回与函数参数之一具有相同类型的数组。还可以方便地定义用于注册
__array_function__
实现的自定义装饰器(比如下面的实现(implements)
)。pythonHANDLED_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调用反射方法的规则相匹配,这确保了检查重载具有可接受的性能,即使存在大量重载参数。- 在 NumPy 1.17 中,默认情况下启用该协议,但可以禁用该协议,设置
class.__array_finalize__
(obj)只要系统在内部从 obj 分配一个新数组,就会调用此方法,其中 obj 是
ndarray
的子类(子类型)。 它可以用于在构造之后更改 self 的属性(例如,以确保二维矩阵), 或者从“父”更新元信息。子类继承此方法的默认实现,该方法不执行任何操作。class.__array_prepare__
(array, context=None)在每个 ufunc 的开头,在具有最高数组优先级的输入对象上调用此方法,如果指定了一个输出对象,则调用此方法。 传入输出数组,返回的任何内容都传递给 ufunc。 子类继承此方法的默认实现,它只是简单地返回未修改的输出数组。 子类可以选择使用此方法将输出数组转换为子类的实例,并在将数组返回到 ufunc 进行计算之前更新元数据。
注意
对于 ufuncs,希望最终弃用这个方法,而不是
__array_ufunc__
。class.__array_wrap__
(array, context=None)在每个 ufunc 的末尾, 对具有最高数组优先级的输入对象或输出对象(如果指定了一个)调用此方法。 传入 ufunc 计算的数组,并将返回的任何内容传递给用户。 子类继承此方法的默认实现,该方法将数组转换为对象类的新实例。 子类可以选择使用此方法将输出数组转换为子类的实例,并在将数组返回给用户之前更新元数据。
注意
对于 ufuncs,希望最终弃用此方法,取而代之的是
__array_ufunc__
。class.__array_priority__
该属性的值用于确定在返回对象的 Python 类型有多种可能性的情况下返回哪种类型的对象。子类继承此属性的默认值 0.0。
注意
对于 ufuncs,希望最终弃用此方法,取而代之的是
__array_ufunc__
。class.__array__
([dtype])如果将具有
__array__
方法的类(是否 ndarray 子类)用作 ufunc 的输出对象, 则结果将写入__array__
返回的对象。在输入数组上进行类似的转换。
矩阵对象
注意
强烈建议不要使用矩阵子类。如下所述,它使得编写一致处理矩阵和规则数组的函数变得非常困难。 目前,它们主要用于与 scipy.sparse
进行交互。 但是,我们希望为此用途提供一种替代方法,并最终删除 matrix
子类。
matrix
对象继承自 ndarray,因此它们具有 ndarray 的相同属性和方法。 但是,当您使用矩阵但希望它们像数组一样工作时,可能会导致意外结果的矩阵对象有六个重要差异:
可以使用字符串表示法创建 Matrix 对象,以允许 Matlab 样式的语法,其中空格分隔列,分号 (';') 分隔行。
Matrix 对象始终是二维的。这具有深远的含义,因为 m.ravel() 仍然是二维的(第一维为 1),并且 item selection 返回二维对象,因此序列行为与数组根本不同。
矩阵对象覆盖乘法成为矩阵乘法。确保您了解您可能希望接收矩阵的函数的这一点。特别是考虑到当 m 是矩阵时 asanyarray(m) 返回矩阵的事实。
矩阵对象超越幂被矩阵提升为幂。关于在使用 asanyarray(…) 的函数中使用电源的相同警告。获取此事实的数组对象保持。
矩阵对象的默认 __array_priority__ 为 10.0,因此与 ndarray 的混合运算始终生成矩阵。
矩阵具有使计算更容易的特殊属性。这些是
方法 描述 matrix.T 返回矩阵的转置。 matrix.H 返回 self 的(复数)共轭转置。 matrix.I 返回可逆 self 的(乘法)逆。 matrix.A 将 self 作为 ndarray 对象返回。
警告
Matrix 对象覆盖乘法 ‘*’ 和 ‘**’(幂),分别为矩阵乘法和矩阵幂。 如果您的子例程可以接受子类,并且您没有转换为基类数组, 则必须使用 ufuncs multiply 和 power 来确保对所有输入执行正确的操作。
Matrix 类是 ndarray 的 Python 子类,可以用作如何构造自己的 ndarray 子类的参考。 矩阵可以从其他矩阵、字符串和任何可以转换为 ndarray
的东西中创建。名称 “mat” 是 NumPy 中 “matrix” 的别名。
方法 | 描述 |
---|---|
matrix(data[, dtype, copy]) | 注意: 不建议再使用这个类,即使是线性的 |
asmatrix(data[, dtype]) | 将输入解析为矩阵。 |
bmat(obj[, ldict, gdict]) | 从字符串、嵌套序列或数组构建矩阵对象。 |
示例 1:从字符串创建矩阵
>>> a=mat('1 2 3; 4 5 3')
>>> print (a*a.T).I
[[ 0.2924 -0.1345]
[-0.1345 0.0819]]
示例 2:从嵌套序列创建矩阵
>>> mat([[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: 从数组创建矩阵
>>> mat(random.rand(3,3)).T
matrix([[ 0.7699, 0.7922, 0.3294],
[ 0.2792, 0.0101, 0.9219],
[ 0.3398, 0.7571, 0.8197]])
内存映射文件数组
内存映射文件对于读取和/或修改具有规则布局的大文件的小段非常有用, 而无需将整个文件读入内存。 ndarray 的一个简单子类使用内存映射文件作为数组的数据缓冲区。 对于小文件,将整个文件读入内存的开销通常不大,但是对于大文件,使用内存映射可以节省大量资源。
内存映射文件数组还有一个额外的方法(除了它们从 ndarray 继承的方法之外):.flush()
, 用户必须手动调用该方法,以确保对数组的任何更改都实际写入磁盘。
方法 | 描述 |
---|---|
memmap | 创建存储在磁盘上二进制文件中的数组的内存映射。 |
memmap.flush(self) | 将数组中的任何更改写入磁盘上的文件。 |
示例:
>>> a = memmap('newfile.dat', dtype=float, mode='w+', shape=1000)
>>> a[10] = 10.0
>>> a[30] = 30.0
>>> del a
>>> b = fromfile('newfile.dat', dtype=float)
>>> print b[10], b[30]
10.0 30.0
>>> a = memmap('newfile.dat', dtype=float)
>>> print a[10], a[30]
10.0 30.0
字符数组(numpy.char
)
注意
chararray
类的存在是为了与 Numarray 向后兼容, 不建议在新开发中使用它。从 numpy 1.4 开始, 如果需要字符串数组,建议使用dtype
object_
、 string_
或 unicode_
的数组, 并使用 numpy.char
模块中的自由函数进行快速矢量化字符串操作。
这些是 string_
类型或 unicode_
类型的增强型数组。 这些数组继承自 ndarray
,但在(逐个)元素的基础上特别定义了操作 +
, *
, 和 %
。 这些操作在字符类型的标准 ndarray
上不可用。 此外,chararray
具有所有标准 string
(和unicode
)方法,在逐个元素的基础上执行它们。 也许创建 chararray 的最简单方法是使用 self.view(chararray)
, 其中 self 是 str 或 unicode 数据类型的 ndarray。 但是,也可以使用 numpy.chararray
构造函数或通过 numpy.char.array
函数创建 chararray:
方法 | 描述 |
---|---|
chararray(shape[, itemsize, unicode, …]) | 提供有关字符串和 unicode 值数组的便捷视图。 |
core.defchararray.array(obj[, itemsize, …]) | 创建一个 chararray。 |
与 str 数据类型的标准 ndarray 的另一个不同之处是 chararray 继承了由 Numarray 引入的特性, 即在项检索和比较操作中,数组中任何元素末尾的空格都将被忽略。
记录数组(numpy.rec
)
NumPy 提供了 recarray
类,允许将结构化数组的字段作为属性进行访问, 以及相应的标量数据类型对象 记录
。
方法 | 描述 |
---|---|
recarray | 构造一个允许使用属性进行字段访问的 ndarray。 |
record | 一种数据类型标量,允许字段访问作为属性查找。 |
掩码数组(numpy.ma
)
标准容器类
为了向后兼容并作为标准的“容器”类, Numeric 的 UserArray 已被引入 NumPy 并命名为 numpy.lib.user_array.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
。 有几种方法可以迭代可能有用的数组:默认迭代,平面迭代和-dimensional 枚举。
默认迭代
ndarray 对象的默认迭代器是序列类型的默认 Python 迭代器。 因此,当数组对象本身用作迭代器时。默认行为相当于:
for i in range(arr.shape[0]):
val = arr[i]
此默认迭代器从数组中选择维度的子数组。 这可以是用于定义递归算法的有用构造。 要遍历整个数组,需要 for 循环。
>>> a = 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]]
Flat 迭代
方法 | 描述 |
---|---|
ndarray.flat | 数组上的一维迭代器。 |
如前所述,ndarray 对象的 flat 属性返回一个迭代器,它将以 C 风格的连续顺序循环遍历整个数组。
>>> for i, val in enumerate(a.flat):
... if i%5 == 0: print i, val
0 10
5 15
10 20
15 25
20 30
在这里,我使用了内置的枚举迭代器来返回迭代器索引和值。
N 维枚举
方法 | 描述 |
---|---|
ndenumerate(arr) | 多维索引迭代器。 |
有时在迭代时获取 N 维索引可能是有用的。ndenumerate 迭代器可以实现这一点。
>>> for i, val in 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 获得。 此对象将对象作为输入,并返回一个迭代器,该迭代器返回元组,提供广播结果中的每个输入序列元素。
>>> for val in broadcast([[1,0],[2,3]],[0,1]):
... print val
(1, 0)
(0, 1)
(2, 0)
(3, 1)