复制与视图#

在操作 NumPy 数组时,可以直接使用 view 访问内部数据缓冲区,而无需复制数据.这确保了良好的性能,但也可能导致不必要的问题,如果用户不知道这是如何工作的.因此,了解这两个术语之间的区别,以及了解哪些操作返回副本,哪些操作返回视图非常重要.

NumPy 数组是一种数据结构,由两部分组成:包含实际数据元素的 contiguous 数据缓冲区和包含有关数据缓冲区的信息的元数据.元数据包括数据类型,步长和其他有助于轻松操作 ndarray 的重要信息. 有关详细信息,请参见 NumPy 数组的内部组织 部分.

视图#

仅通过更改某些元数据(如 stridedtype )而不更改数据缓冲区,就可以不同地访问数组.这会创建一个新的数据查看方式,这些新数组称为视图.数据缓冲区保持不变,因此对视图所做的任何更改都会反映在原始副本中.可以通过 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

在此, y 是一个副本,由 base 属性表示. 我们还可以通过将新值分配给 x[[1, 2]] 来确认这一点,这反过来不会影响 y

>>> 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.

以另一个操作为例, ravel 返回数组的连续展平视图(如果可能). 另一方面, ndarray.flatten 始终返回数组的展平副本. 但是,为了在大多数情况下保证视图, x.reshape(-1) 可能是更可取的.

如何判断数组是视图还是副本#

ndarray 的 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 的视图还是副本.