副本和视图#
在操作NumPy数组时,可以通过 视图 直接访问内部数据缓冲区,而无需复制数据.这确保了良好的性能,但如果用户不了解其工作原理,也可能导致不必要的问题.因此,了解这两个术语的区别以及哪些操作返回副本,哪些操作返回视图是非常重要的.
NumPy 数组是一个由两部分组成的数据结构:包含实际数据元素的 连续 数据缓冲区和包含有关数据缓冲区信息的元数据.元数据包括数据类型、步幅和其他重要信息,有助于轻松操作 ndarray
.详见 NumPy 数组的内部组织 部分.
视图#
通过更改某些元数据(如 步幅 和 数据类型)而不更改数据缓冲区,可以以不同的方式访问数组.这创建了一种查看数据的新方式,这些新数组被称为视图.数据缓冲区保持不变,因此对视图所做的任何更改都会反映在原始副本中.可以通过 ndarray.view
方法强制创建视图.
复制#
当通过复制数据缓冲区和元数据创建一个新数组时,这称为复制.对副本所做的更改不会反映在原始数组上.制作副本较慢且消耗内存,但有时是必要的.可以通过使用 ndarray.copy
强制进行复制.
索引操作#
当元素可以在原始数组中通过偏移量和步幅进行访问时,视图就会被创建.因此,基本索引总是创建视图.例如:
>>> import numpy as np
>>> x = np.arange(10)
>>> x
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> y = x[1:3] # creates a view
>>> y
array([1, 2])
>>> x[1:3] = [10, 11]
>>> x
array([ 0, 10, 11, 3, 4, 5, 6, 7, 8, 9])
>>> y
array([10, 11])
在这里,当 x
改变时,``y`` 也会改变,因为它是一个视图.
高级索引, 另一方面,总是创建副本.例如:
>>> import numpy as np
>>> x = np.arange(9).reshape(3, 3)
>>> x
array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
>>> y = x[[1, 2]]
>>> y
array([[3, 4, 5],
[6, 7, 8]])
>>> y.base is None
True
Here, ``y`` is a copy, as signified by the :attr:`base <.ndarray.base>`
attribute. We can also confirm this by assigning new values to ``x[[1, 2]]``
which in turn will not affect ``y`` at all::
>>> x[[1, 2]] = [[10, 11, 12], [13, 14, 15]]
>>> x
array([[ 0, 1, 2],
[10, 11, 12],
[13, 14, 15]])
>>> y
array([[3, 4, 5],
[6, 7, 8]])
必须注意的是,在分配 x[[1, 2]]
时,不会创建视图或副本,因为分配是原地进行的.
其他操作#
numpy.reshape
函数在可能的情况下创建一个视图,否则创建一个副本.在大多数情况下,可以通过修改步长来使用视图重塑数组.然而,在某些情况下,数组变得不连续(可能在 ndarray.transpose
操作之后),重塑不能通过修改步长来完成,需要一个副本.在这些情况下,我们可以通过将新形状赋值给数组的 shape 属性来引发错误.例如:
>>> import numpy as np
>>> x = np.ones((2, 3))
>>> y = x.T # makes the array non-contiguous
>>> y
array([[1., 1.],
[1., 1.],
[1., 1.]])
>>> z = y.view()
>>> z.shape = 6
Traceback (most recent call last):
...
AttributeError: Incompatible shape for in-place modification. Use
`.reshape()` to make a copy with the desired shape.
以另一个操作为例,:func:ravel 在可能的情况下返回数组的连续展平视图.另一方面,:meth:ndarray.flatten 总是返回数组的展平副本.然而,为了在大多数情况下保证视图,``x.reshape(-1)`` 可能是更好的选择.
如何判断数组是视图还是副本#
base
属性使得判断一个数组是视图还是副本变得容易.视图的 base 属性返回原始数组,而副本的 base 属性返回 None
.
>>> import numpy as np
>>> x = np.arange(9)
>>> x
array([0, 1, 2, 3, 4, 5, 6, 7, 8])
>>> y = x.reshape(3, 3)
>>> y
array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
>>> y.base # .reshape() creates a view
array([0, 1, 2, 3, 4, 5, 6, 7, 8])
>>> z = y[[2, 1]]
>>> z
array([[6, 7, 8],
[3, 4, 5]])
>>> z.base is None # advanced indexing creates a copy
True
注意,``base`` 属性不应被用来确定一个 ndarray 对象是否是 新的;只有当它是另一个 ndarray 的视图或副本时才应使用.