副本和视图#
在 NumPy 数组上操作时,可以使用视图直接访问内部数据缓冲区,而无需复制数据。这可以确保良好的性能,但如果用户不知道其工作原理,也可能会导致不必要的问题。因此,了解这两个术语之间的区别以及哪些操作返回副本和哪些操作返回视图非常重要。
NumPy 数组是一个由两部分组成的数据结构:包含实际数据元素的连续数据缓冲区和包含有关数据缓冲区信息的元数据。元数据包括数据类型、步幅和其他有助于轻松操作的重要信息ndarray
。
有关详细信息,请参阅NumPy 数组的内部组织部分。
看法#
只需更改某些元数据(如步幅和数据类型)而不更改数据缓冲区,就可以以不同的方式访问数组。这创建了一种查看数据的新方式,这些新数组称为视图。数据缓冲区保持不变,因此对视图所做的任何更改都会反映在原始副本中。可以通过该方法强制查看
ndarray.view
。
复制#
当通过复制数据缓冲区和元数据创建新数组时,它称为副本。对副本所做的更改不会反映在原始数组上。制作副本速度较慢且消耗内存,但有时是必要的。可以使用 强制复制
ndarray.copy
。
索引操作#
也可以看看
当可以使用原始数组中的偏移量和步幅来寻址元素时,就会创建视图。因此,基本索引总是创建视图。例如:
>>> 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])
在这里,当发生变化y
时,它就会发生变化,因为它是一个视图。x
另一方面,高级索引总是创建副本。例如:
>>> 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
操作之后),无法通过修改步幅来完成重塑,并且需要副本。在这些情况下,我们可以通过将新形状分配给数组的形状属性来引发错误。例如:
>>> 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
可以很容易地判断一个数组是视图还是副本。视图的基本属性返回原始数组,同时返回None
副本。
>>> 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 的视图或副本时。