可変オブジェクトの参照伝播 — コピーの落とし穴
このトピックを終えると
Pythonでリストを = でコピーした場合に、なぜ元のオブジェクトが変更されるのかを理解し、浅いコピーと深いコピーを区別して使用できるようになります。
コピーしたのに元のオブジェクトが変更される
original = [1, 2, 3]copy = original
copy.append(4)print(original) # [1, 2, 3, 4] — 元のオブジェクトも変更される!copy = original は コピーではありません。 同じリストに別の名前を付けただけです。original と copy は、同じリストを指す 2 つの名前です。
前のトピック(変数とメモリ)で、変数は「値を格納する箱」ではなく、「オブジェクトを指す矢印」であると学びました。それが実際に問題を引き起こす瞬間が、まさにここです。
print(id(original)) # 4392957504print(id(copy)) # 4392957504 — 同じアドレス!本物のコピー — スライス
original = [1, 2, 3]copy = original[:] # 完全なスライス = 新しいリスト
copy.append(4)print(original) # [1, 2, 3] — 元のオブジェクトは変更されないprint(copy) # [1, 2, 3, 4][:] は、リストの最初から最後までをスライスして 新しいリストを作成します。 list(original) や original.copy() も同じ効果があります。
浅いコピーの限界
matrix = [[1, 2], [3, 4]]shallow = matrix[:]
shallow[0][0] = 99print(matrix) # [[99, 2], [3, 4]] — 元のオブジェクトも変更される!スライスは 1 段階だけコピーします。 matrix[:] は、外側のリストを新しく作成しますが、内側の [1, 2] と [3, 4] は 同じオブジェクトを共有します。 内側を修正すると、元のオブジェクトにも反映されます。
これが 浅いコピー (shallow copy) です。
深いコピー — copy.deepcopy
import copy
matrix = [[1, 2], [3, 4]]deep = copy.deepcopy(matrix)
deep[0][0] = 99print(matrix) # [[1, 2], [3, 4]] — 元のオブジェクトは変更されないdeepcopy は、内部オブジェクトまですべて再帰的にコピーします。完全に独立した新しいオブジェクトです。
いつ何を使いましょうか
| 状況 | 方法 | 理由 |
|---|---|---|
| 1 次元のリスト | lst[:] または lst.copy() | 高速かつ簡単 |
| 2 次元以上のネストされたリスト | copy.deepcopy(lst) | 内部リストまで独立させる必要がある |
| 辞書 | dict.copy() または {**d} | 1 段階の浅いコピー |
| ネストされた辞書 | copy.deepcopy(d) | 内部の辞書/リストまで独立させる |
| 実際にコピーする必要がない場合 | = のまま | 意図的に同じオブジェクトを共有する場合 |
関数引数でも同じ
def add_item(lst, item): lst.append(item) return lst
my_list = [1, 2, 3]result = add_item(my_list, 4)
print(my_list) # [1, 2, 3, 4] — 元のオブジェクトが変更される関数にリストを渡すと、参照が渡されます。 関数内で .append() を行うと、元のオブジェクトも変更されます。 元のオブジェクトを保持するには、関数内で最初にコピーしてください。
def add_item_safe(lst, item): new_lst = lst[:] new_lst.append(item) return new_lst可変 vs 不変のまとめ
| 型 | 可変/不変 | = コピー後、修正した場合の元のオブジェクトへの影響 |
|---|---|---|
int、float、str、tuple | 不変 | 影響なし (いずれにせよ新しいオブジェクトが作成される) |
list、dict、set | 可変 | 元のオブジェクトも変更される |
不変オブジェクトでは、この問題は発生しません。 文字列を「修正」すると、常に新しい文字列が作成されるためです。 可変オブジェクトでのみ注意が必要です。
重要なポイント
=はコピーではなく、同じオブジェクトに別の名前を付けることです。 1 次元は[:]または.copy()、ネストされた構造はcopy.deepcopy()を使用します。 関数に可変オブジェクトを渡すと、元のオブジェクトが変更される可能性があります — 意図しない場合は、最初にコピーしてください。