コピーと閲覧数

チャーリーはサブ配列を変更したところ、元の配列も変更されてしまっていたことに気づき、2時間もデバッグに費やした。アリスはこう言う。「.base属性を確認して。もしbase isが元の配列なら、それはビューだ。迷ったら、.copy()。」

この事例は、重要な問題を浮き彫りにしています。すなわち、NumPyの演算のすべてが独立した配列を返すわけではないということです。微妙なデータ改変バグを回避するためには、「ビュー」と「コピー」の違いを理解することが不可欠です。


1. ビューの仕組み

(1) ビューとは何か

ビューは、元の配列へのもうひとつの「窓」であり、同じメモリを共有しています。ビューを変更すると、元の配列が直接変更されます。

PYTHON
import numpy as np

a = np.array([1, 2, 3, 4, 5])
b = a[1:4]          # slice returns a view
b[0] = 999
print(a)            # [  1 999   3   4   5]  original changed!
print(b)            # [999   3   4]
TEXT
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.

ビューの利点はゼロコピーである――余分なメモリを消費せず、極めて高速だ。

(2) 記憶上の関係

PYTHON
a = np.array([10, 20, 30])
b = a[:]            # view
print(a.__array_interface__['data'][0])  # original data address
print(b.__array_interface__['data'][0])  # view data address (same!)
TEXT
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.

どちらも同じメモリの開始アドレスを指しており、異なるのはメタデータ(形状、ストライド)のみです。

(3) .base 属性

PYTHON
a = np.arange(12).reshape(3, 4)
b = a[0:2, 1:3]  # slice view
c = a[[0, 2]]    # fancy indexing — copy

print(b.base is a)  # True — view
print(c.base is a)  # False — copy


2. ビューを返す操作とコピーを返す操作の違い

(1) ビュー(共有メモリ、コピーなし)

操作
基本的なスライシング a[1:5], a[1:5:2], a[:, 2:4]
.view() a.view(np.float32)
reshape a.reshape(3, 4)
transpose / .T a.T, a.transpose(1, 0)
ravel (通常) a.ravel()
squeeze a.squeeze()
newaxis a[:, np.newaxis]

(2) コピー(新しいメモリ、独立)

操作
.copy() a.copy()
凝った索引 a[[0, 2, 4]]
ブール索引 a[a > 0]
astype a.astype(np.float32)
flatten a.flatten()
np.where np.where(a > 0, a, 0)

(1) ▶ サンプル

TEXT
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.

: .base を使って「view」と「copy」を比較する(難易度 ⭐)

PYTHON
import numpy as np

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

# Basic slice — view
b = a[0:2]
print(f"slice base is a: {b.base is a}")  # True

# Reshape — view
c = a.reshape(6, 2)
print(f"reshape base is a: {c.base is a}")  # True

# Fancy indexing — copy
d = a[[0, 2]]
print(f"fancy base is a: {d.base is a}")  # False

# Boolean indexing — copy
e = a[a > 5]
print(f"boolean base is a: {e.base is a}")  # False
TEXT
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
⚠️ 注意: 以下のコードは、ローカルの Python 環境で実行する必要があります。



3. 比較表

(1) 「表示」と「コピー」の決定木

TEXT
Is it a basic slice (start:stop:step)?
  ├── Yes → View (shared memory)
  └── No → Is it fancy/boolean indexing?
      ├── Yes → Copy (new memory)
      └── No → Check .base attribute
```text

### (2) パフォーマンスへの影響

| 演算 | 時間 (100万要素) | メモリ |
|-----------|-------------------|--------|
| ビュー(スライス) | O(1) | 共有 |
| コピー(高度な) | O(n) | 新規割り当て |
| .copy() | O(n) | 新規割り当て |
| 型 | O(n) | 新規割り当て |
### (1) ▶ サンプル:ビューの形状変更と転置(難易度 ⭐⭐)

```python
import numpy as np

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

# Reshape returns a view
b = a.reshape(6, 2)
print(f"reshape base is a: {b.base is a}")

# Transpose returns a view
c = a.T
print(f"transpose base is a: {c.base is a}")

# Modifying the view changes the original
c[0, 0] = 999
print(f"a[0, 0] after transpose mod: {a[0, 0]}")

出力:

TEXT
reshape base is a: True
transpose base は a: True
a[0, 0] を転置した後、mod 999 で計算すると:999

(2) ▶ サンプル:flatten 対 ravel — コピー 対 ビュー (難易度 ⭐⭐)

PYTHON
import numpy as np

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

# ravel usually returns a view
r = a.ravel()
print(f"ravel base is a: {r.base is a}")

# flatten always returns a copy
f = a.flatten()
print(f"flatten base is a: {f.base is a}")

# After transpose, ravel may need to copy
t = a.T
r2 = t.ravel()
print(f"ravel of transposed base is a: {r2.base is a}")

出力:

TEXT
ravel base は a です: True
flatten base は a: False です
塩基の転位に関するラベルは a:False


❓ よくある質問

Q ある配列が別の配列のビューであるかどうかを確認するにはどうすればよいですか?
A arr.base is original を確認してください。True の場合はビューです。arr.base が None の場合は、その配列は独自のデータを保持しています。
Q 異なるデータ型でビューを作成することはできますか?
A はい。a.view(np.float32) を使用すると、同じバイト列を別のデータ型として再解釈します。これは危険であり、必要となることはほとんどありません。変換を伴う安全なコピーを行うには、astype を使用してください。
Q ravel はなぜコピーを返すことがあるのですか?
A ravel は、配列がメモリ上で連続している場合にのみビューを返します。転置などの操作を行った後、配列が非連続になっている可能性があり、その場合は ravel はコピーを返すようになります。
Q .copy() はどのような場合に使うべきですか?
A (1) サブ配列を変更しようとしており、元の配列に影響を与えたくない場合、(2) C言語レベルの操作のために連続した配列が必要な場合、(3) メモリの参照を明示的に解除したい場合。

📖 まとめ



📝 練習問題

  1. 初心者向け(難易度 ⭐):10要素の1次元配列を作成してください。スライス、特殊なインデックス、ブール値によるマスク選択を作成してください。それぞれについて、結果を変更した際に元の値が変更されるかどうかを確認してください。

  2. 中級(難易度 ⭐⭐):3×4の配列を作成し、その中から2×2のスライスを取り出して変更を加えます。元の配列が変化する様子を確認してください。次に、.copy() を使用して同じ操作を繰り返してください。元の配列が変化しないことを確認してください。

  3. 上級(難易度 ⭐⭐⭐):3次元配列を作成し、それを転置してから、その結果の形状を変更してみてください。形状変更によってビューが返されるかどうかを確認してください(.base を使用してください)。返される理由、または返されない理由を説明してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%