Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonの浅いコピー(シャローコピー)と深いコピー(ディープコピー)の違いをわかりやすく解説

Pythonにおける変数とオブジェクト参照の基本

Pythonでは、変数はオブジェクトへの「参照」にすぎません。そのため、ある変数を別の変数に代入しても、オブジェクトそのものが複製されるわけではなく、同じオブジェクトへの新たな参照が作られるだけです。この挙動は、組み込み関数 id() を使うことで確認できます。

>>> L1 = [1,2,3]
>>> L2 = L1
>>> id(L1), id(L2)
(2165544063496, 2165544063496)

上記のコードの実行結果を見ると、両方のリストオブジェクトに対する id() の戻り値が同一であることがわかります。つまり、L1とL2は同じオブジェクトを参照しているのです。この状態のL2は、L1の浅いコピー(シャローコピー)と呼ばれます。両者が同じオブジェクトを指しているため、どちらか一方に加えた変更は、もう一方にも反映されます。

>>> L1 = [1,2,3]
>>> L2 = L1
>>> L2[1] = 100
>>> L1,L2
([1, 100, 3], [1, 100, 3])

この例では、L2のインデックス1の要素を変更しました。すると、その変更がL1とL2の両方に現れていることが確認できます。

浅いコピーと深いコピーとは何か

コピーオペレーションによって完全に新しいオブジェクトが生成され、さらにネストされた(入れ子になった)オブジェクトのコピーも再帰的に追加される場合、そのコピーは深いコピー(ディープコピー)と呼ばれます。

Pythonの標準ライブラリには copy モジュールが用意されており、次の2つのメソッドが提供されています。

  • copy.copy() — 浅いコピー(シャローコピー)を作成する
  • copy.deepcopy() — 深いコピー(ディープコピー)を作成する

浅いコピー(copy.copy())の挙動

浅いコピーは新しいオブジェクトを生成し、元の要素への参照を格納します。しかし、ネストされたオブジェクト自体のコピーは作らず、ネストされたオブジェクトへの参照だけを複製します。そのため、コピー処理は再帰的には行われません。

>>> import copy
>>> L1 = [[1,2,3], [4,5,6]]
>>> L2 = copy.copy(L1)
>>> L1,L2
([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]])
>>> L2.append([10,10,10])
>>> L1,L2
([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6], [10, 10, 10]])

ここで、L1はネストされたリストであり、L2はその浅いコピーです。親リストオブジェクトへの参照はL2にコピーされますが、内部の要素まではコピーされません。したがって、L2に対して別のリストを追加(append)しても、その変更はL1には反映されません。

一方、子要素(内側のリスト)の要素を変更しようとすると、その影響は両方のリストに現れます。

>>> L1 = [[1,2,3], [4,5,6]]
>>> L2 = copy.copy(L1)
>>> L1,L2
([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]])
>>> L2[1][0] = 100
>>> L1,L2
([[1, 2, 3], [100, 5, 6]], [[1, 2, 3], [100, 5, 6]])

これは、浅いコピーでは内側のリストが共有されているためです。外側のリスト構造の変更(要素の追加や削除など)は独立していますが、内側のオブジェクトへの変更は元のデータにも影響を与える点に注意が必要です。

深いコピー(copy.deepcopy())の挙動

深いコピーの場合は、新しいオブジェクトを生成するとともに、元の要素に含まれるネストされたオブジェクトのコピーも再帰的に追加します。

次の例では、L2はL1の深いコピーです。この状態で内側のリストの任意の要素を変更しても、もう一方のリストには影響しません。

>>> L1 = [[1,2,3], [4,5,6]]
>>> L2 = copy.deepcopy(L1)
>>> L1,L2
([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]])
>>> L2[1][0] = 100
>>> L1,L2
([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [100, 5, 6]])

出力結果から、L2の変更がL1に一切反映されていないことが確認でき、深いコピーの効果が実証されています。

まとめ:使い分けのポイント

  • 単純な代入(=):同じオブジェクトを共有するため、すべての変更が相互に反映される
  • 浅いコピー(copy.copy()):最上位のオブジェクトは独立するが、ネストされたオブジェクトは共有される
  • 深いコピー(copy.deepcopy()):ネストされたオブジェクトまで完全に複製されるため、完全に独立したデータとなる

ネストされたデータ構造を安全に扱いたい場合は深いコピーを、パフォーマンスを重視して一部の共有を許容できる場合は浅いコピーを選ぶなど、用途に応じて適切に使い分けることが重要です。

  1. Javaのシャローコピーとディープコピーの違いを実例で徹底解説

    シャローコピー(浅いコピー)とディープコピー(深いコピー)は、どちらも指定されたオブジェクトの複製を作成するための手法ですが、その仕組みと動作には重要な違いがあります。本記事では、それぞれの特徴をサンプルコードと実行結果をもとにわかりやすく解説します。 シャローコピー(浅いコピー) シャローコピーでは、オブジェクトの新しいインスタンスを作成し、元のオブジェクトからデータをコピーします。ただし、配列などの参照型フィールドに関しては、データの実体ではなく「参照」だけが渡される点に注意が必要です。そのため、コピー元のデータを変更すると、コピー先のオブジェクトにも同じ変更が反映されてしまいます。この

  2. PythonとTkinterを使ってクリップボードの内容を取得して表示する方法

    クリップボードからデータをコピー(取得)するには、Tkinterが提供する clipboard_get() メソッドを使用します。この記事では、実際のコード例を通じて、クリップボードからテキストデータを取得し、Tkinterウィンドウ上に表示する方法を解説します。 実装の手順 tkinterライブラリをインポートし、tkinterフレーム(ウィンドウ)のインスタンスを作成します。 geometry メソッドを使って、ウィンドウのサイズを設定します。 clipboard_get() を呼び出してクリップボードからテキストを取得し、変数 cliptext に格納します。 取得したテキストを表示す