python 浅い コピー 深い コピーは入れ子でどう違う?

要点

  • 入れ子のリストを .copy() した後、内側の要素を書き換えると元のリストも変化した。(出典: 自社の実測・2026-10-25)
  • 同じ構造で copy.deepcopy() を使った場合は、コピー側の変更が元のリストに影響しなかった。(出典: 自社の実測・2026-10-25)
  • 数値だけの1階層のリストでは、.copy() 後に要素を置き換えても元のリストは変化しなかった。(出典: 自社の実測・2026-10-25)

コピーしたのに元のリストまで変わるのはなぜか

リストをコピーして編集用に使うとき、確かめたいのは「新しいリストができたか」だけではない。「コピー側を書き換えたとき、元のデータが保たれるか」も確認する必要がある。入れ子のリストでは、この2つは同じ意味にならない。

今回の記録では、a = [[1, 2], [3, 4]] に対して b = a.copy() を実行し、b[0][0] を書き換えている。その結果、b だけでなく a の内側の要素も変化した。一方、外側のリストについて a is b を調べた結果は False だった。

つまり、元のリストまで変化したことを、単に「コピーが作られていなかった」と説明することはできない。外側は別のリストになっているが、その中にあるリストは共有されていた。編集用のデータを作る処理では、外側が別になったことだけを確認しても、入れ子の要素が独立したかどうかは分からない。

ここで比較するのは、Pythonの .copy() と copy.deepcopy()、そして入れ子の有無による違いだ。実測日は2026-10-25で、記録されたPythonの実行環境は Python 3.9.0 である。

出典: 自社の実測(2026-10-25)

公式の「浅いコピー」は何を複製すると書いているか

浅いコピーとは、新しい複合オブジェクトを作り、その中に元のオブジェクトに含まれる対象への参照を入れるコピーのことだ。

Python公式ドキュメントの copy モジュールについて、記録には次の抜粋がある。省略記号も記録のまま引用する。

A shallow copy constructs a new compound object… but… inserts references into it, to the objects found in the original

出典: https://docs.python.org/3/library/copy.html (取得日 2026-10-25)

この記述で注目したいのは、「新しい複合オブジェクトを作る」ことと、「元の対象への参照を入れる」ことが両立している点だ。浅いコピーは、新しい外側のリストを作る。しかし今回の入れ子の例では、内側の [1, 2] 自体を別のリストとして複製していない。

そのため、「コピー」という操作名だけでは、どこまで独立したのかを判断できない。今回の問いに答えるには、外側のリストと内側のリストを分けて見る必要がある。公式の記述にある参照の扱いは、実測で外側が別、内側が同じになった結果と対応している。

浅いコピー・深いコピー・入れ子なしを比べる

以下は、記録にあるPythonの結果を整理した表だ。変更後の値を比較する際は、浅いコピーの例と深いコピーの例で、それぞれ元のリストを作り直している点にも注意したい。

比較対象 コピー方法 コピー後の変更 変更後の元のリスト a 同一性の確認
入れ子あり・浅いコピー b = a.copy() b[0][0] = 99 [[99, 2], [3, 4]] a is b は False、a[0] is b[0] は True
入れ子あり・深いコピー c = copy.deepcopy(a) c[0][0] = -1 [[1, 2], [3, 4]] a[0] is c[0] は False
数値だけ・入れ子なし b = a.copy() b[0] = 99 [1, 2, 3] 記録なし

浅いコピーの実測コードと結果は、次のとおり記録されている。

a = [[1, 2], [3, 4]]
b = a.copy()
b[0][0] = 99
print(a)  # [[99, 2], [3, 4]]
print(b)  # [[99, 2], [3, 4]]
print(a is b, a[0] is b[0])  # False True

最後の行が、コピーの深さを読み取る手がかりになる。False True は、外側の a と b は同じオブジェクトではなく、内側の a[0] と b[0] は同じオブジェクトであるという結果だ。表示されたリストの値だけでなく、どの対象が共有されているかも確かめている。

深いコピーの例では、内側のリストについても同一性の確認結果が False だった。コピー側の内側を書き換えても、元のリストは変更前の値を保っていた。

出典: 自社の実測(2026-10-25)

どちらを使うかは、変更する場所で考える

今回のように、入れ子のリストをコピーしてから内側の要素を編集し、元の値を残したい場合は、実測でその動作を確認できた copy.deepcopy() が選択肢になる。設定データやフォームの入力値を編集用にコピーする場面でも、内側に書き換え可能な対象があるかを確認したい。

一方、数値だけの [1, 2, 3] を .copy() し、コピー側の b[0] を置き換えた例では、元のリストは変化しなかった。この結果と入れ子の例の違いは、単にコピー方法だけではない。変更している場所も、外側のリストの要素なのか、共有されている内側のリストの要素なのかで異なる。

したがって、今回の結果から「浅いコピーを使うと必ず元のデータが変わる」とは言えない。逆に、数値だけのリストで元データが保たれたことを根拠に、入れ子のリストでも独立していると判断することもできない。

確認範囲にも限りがある。記録で測った入れ子は2階層までで、3階層以上や循環参照の挙動は測っていない。処理速度の比較も記録にはない。ここでの選択基準は、示されたリスト構造と変更操作に対して、元のリストに影響したかどうかである。

自分の環境で確かめる手順

まずPythonの版を確認し、その後、浅いコピーと深いコピーを別々に実行する。次のコマンドは、記録にある操作を自分の環境で確かめるためのものだ。実行結果はここには掲載しない。

python3 --version
python3 -c 'a = [[1, 2], [3, 4]]; b = a.copy(); b[0][0] = 99; print(a); print(b); print(a is b, a[0] is b[0])'
python3 -c 'import copy; a = [[1, 2], [3, 4]]; c = copy.deepcopy(a); c[0][0] = -1; print(a); print(c); print(a[0] is c[0])'
python3 -c 'a = [1, 2, 3]; b = a.copy(); b[0] = 99; print(a)'

見る場所は、変更後の元のリストと、内側のリストの同一性だ。外側が別のリストになっていることと、内側まで別になっていることを分けて確認する。それぞれのコマンドで元のリストを作り直すことで、前の操作で変更した値を次の比較に持ち込まずに確かめられる。

比較元となる記録の環境は Python 3.9.0 である。自分の環境の版と結果を一緒に残せば、今回の記録との対応も確認できる。

出典: 自社の実測(2026-10-25)

コメント

タイトルとURLをコピーしました