6.7. ビューとコピー¶
ビュー は、ソースと同じデータブロックへの 2 つ目の窓です。データはコピーされません。ビューは新しいディスクリプタ(独自の形状、ストライド、dtype)を保持しますが、バッファは共有します。ビューは事実上コストがかかりません。
コピー はカメラに新しいバッファを要求し、ソースをたどってそれを埋めます。コピーは時間と RAM の両方を消費します。
形状を変える操作のほとんどはビューを生成します。データを変換する操作のほとんどはコピーを生成します。どちらがどちらかを知ることが、ホットループがカメラの RAM を使い果たすかどうかを決めます。
6.7.1. Reshape¶
reshape() は要求された形状の配列を返します。要素の総数は変わってはならず、変わると ValueError が送出されます:
a = np.arange(12, dtype=np.uint8)
m = a.reshape((3, 4))
結果はビューであり、m と a はデータを共有します。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, 1, 2, 3, 4, 5 順ではなく、メモリ位置 0, 3, 1, 4, 2, 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;asarray()(dtype が一致する場合)。
次の操作は コピー を返します:
ブールインデックス --
a[mask];算術 --
a + b、a * 2、np.sin(a);array()-- 別の配列からであっても常にコピーします;
明示的なコピーに頼るのは、独立したバッファが本当に必要なときだけにしてください。RAM が限られたカメラでは、ビューとコピーの違いが、収まるコードと収まらないコードとの違いになることがしばしばあります。