副本與檢視#

當操作 NumPy 陣列時,可以直接使用檢視存取內部資料緩衝區,而無需複製資料。這確保了良好的效能,但如果使用者不了解其運作方式,也可能導致不必要的問題。因此,了解這兩個術語之間的差異,以及知道哪些操作返回副本,哪些返回檢視,非常重要。

NumPy 陣列是一種資料結構,由兩個部分組成:包含實際資料元素的連續資料緩衝區,以及包含有關資料緩衝區資訊的中繼資料。中繼資料包括資料型別、步幅和其他重要資訊,這些資訊有助於輕鬆操作ndarray。有關詳細資訊,請參閱NumPy 陣列的內部組織章節。

檢視#

可以僅通過更改某些中繼資料(如步幅dtype)來不同地存取陣列,而無需更改資料緩衝區。這創建了一種查看資料的新方式,這些新陣列稱為檢視。資料緩衝區保持不變,因此對檢視所做的任何更改都會反映在原始副本中。可以通過ndarray.view方法強制創建檢視。

副本#

當通過複製資料緩衝區以及中繼資料來創建新陣列時,它被稱為副本。對副本所做的更改不會反映在原始陣列上。創建副本速度較慢且耗費記憶體,但有時是必要的。可以使用ndarray.copy強制創建副本。

索引操作#

另請參閱

ndarray 上的索引

當可以使用原始陣列中的偏移量和步幅來定址元素時,就會創建檢視。因此,基本索引始終創建檢視。例如

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

以另一個操作為例,ravel在可能的情況下返回陣列的連續展平檢視。另一方面,ndarray.flatten始終返回陣列的扁平化副本。但是,為了在大多數情況下保證檢視,x.reshape(-1)可能是更佳的選擇。

如何判斷陣列是檢視還是副本#

ndarray 的 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 的檢視還是副本。