6.7. 視圖與複本

視圖(view)是面向與來源相同資料區塊的第二個視窗。不會複製任何資料;視圖持有一個全新的描述子(有自己的形狀、步幅與 dtype),但共享緩衝區。視圖的成本基本上是零。

複本(copy)會向相機要求一個新緩衝區,並走訪來源把它填滿。複本同時耗費時間與 RAM。

大多數改變形狀的方法會產生視圖。大多數轉換資料的方法則會產生複本。知道何者為何,決定了一個熱迴圈是否會讓相機耗盡 RAM。

6.7.1. Reshape

reshape() 會傳回一個具有所要求形狀的陣列。元素總數必須維持不變,否則會引發 ValueError:

a = np.arange(12, dtype=np.uint8)
m = a.reshape((3, 4))

結果是一個視圖——ma 共享資料。透過 m[0, 0] = 99 寫入也會改變 a[0]

把一個新的元組指派給 shape 是相同運算的簡寫:

a = np.arange(9)
a.shape = (3, 3)

6.7.2. Transpose

transpose()(或 .T 捷徑)會反轉各軸。它透過反轉步幅來實作——不會移動任何資料:

m = np.arange(6, dtype=np.uint8).reshape((2, 3))
t = m.T                  # shape (3, 2), shares m's buffer

轉置後的視圖不會連續地走訪資料區塊。逐列讀取上面的 t 會走訪記憶體位置 0, 3, 1, 4, 2, 5,而非位元組實際排列的底層 0, 1, 2, 3, 4, 5 順序。普通的算術運算與縮減運算可妥善處理這種情況——它們會依步幅逐步走訪——但 tobytes() 不行,因為它會直接交回底層緩衝區而不複製。緩衝區所保存的位元組與視圖形狀所暗示的順序不符,因此該方法對任何非連續視圖都會引發 ValueError。當需要以轉置順序取得位元組時,請先強制產生一份全新的連續複本:

bytes_out = t.copy().tobytes()

6.7.3. Flatten 與 flat

flatten() 會傳回陣列的 1 維 複本:

f = m.flatten()          # new dense 1-D ndarray

傳入 order='C'(預設)以先走訪最後軸,或傳入 order='F' 以先走訪第一軸:

m = np.arange(6, dtype=np.uint8).reshape((2, 3))
# m = [[0, 1, 2],
#      [3, 4, 5]]
m.flatten()              # array([0, 1, 2, 3, 4, 5], dtype=uint8)
m.flatten(order='F')     # array([0, 3, 1, 4, 2, 5], dtype=uint8)

flat迭代器 形式。它會把任意階 ndarray 的每個元素以純量形式產出,而不配置扁平複本:

for x in m.flat:
    print(x)

當應用程式需要 走訪 每個元素時,請優先使用 flat;當它需要一個密集的 1 維緩衝區交給另一個函式時,請使用 flatten()

6.7.4. 迭代

迭代 1 維陣列會產出純量;迭代較高階陣列會產出 (n-1) 維的視圖:

m = np.array([[0, 1, 2], [3, 4, 5]], dtype=np.uint8)
for row in m:
    print(row)               # array([0, 1, 2]), array([3, 4, 5])

迭代矩陣所產出的各列都是視圖,因此修改它們即會修改來源。

6.7.5. 複本

copy() 是取得一個獨立 ndarray 的明確方式,其修改不會影響原始陣列。它會配置一個新緩衝區,並把來源走訪進去:

c = a.copy()

tobytes() 會傳回一個與陣列資料區塊共享記憶體的 bytearray。透過該 bytearray 寫入會就地修改陣列。如果陣列不是密集的(切片視圖、轉置……),會引發 ValueError

tolist() 會把內容以可能巢狀的 Python list 形式傳回。對於序列化小型結果很有用;對於大型結果則昂貴,因為每個元素都會變成一個獨立的 Python 物件。

6.7.6. 哪些運算傳回哪一種

完整規則:

以下運算會傳回 視圖

  • 切片——a[1:5]a[::2]m[:, 0]

  • 對較高階陣列的單軸索引——m[0]

  • 迭代一個 n 維陣列;

  • reshape(),當所要求的配置相容時;

  • transpose() / .T

  • frombuffer()

  • asarray(),當 dtype 相符時。

以下運算會傳回 複本

  • copy()

  • flatten()

  • 布林索引——a[mask]

  • 算術運算——a + ba * 2np.sin(a)

  • array()——一律複製,即使是從另一個陣列複製;

  • concatenate()

只有在真正需要一個獨立緩衝區時才使用明確的複本。在 RAM 有限的相機上,視圖與複本的差別往往就是放得下的程式碼與放不下的程式碼之間的差別。