6.7. ビューとコピー

ビュー は、ソースと同じデータブロックへの 2 つ目の窓です。データはコピーされません。ビューは新しいディスクリプタ(独自の形状、ストライド、dtype)を保持しますが、バッファは共有します。ビューは事実上コストがかかりません。

コピー はカメラに新しいバッファを要求し、ソースをたどってそれを埋めます。コピーは時間と 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, 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;

  • frombuffer();

  • asarray()(dtype が一致する場合)。

次の操作は コピー を返します:

  • copy();

  • flatten();

  • ブールインデックス -- a[mask];

  • 算術 -- a + ba * 2np.sin(a);

  • array() -- 別の配列からであっても常にコピーします;

  • concatenate()

明示的なコピーに頼るのは、独立したバッファが本当に必要なときだけにしてください。RAM が限られたカメラでは、ビューとコピーの違いが、収まるコードと収まらないコードとの違いになることがしばしばあります。