このチュートリアルでは、Pythonの例を用いて、浅いコピーと深いコピーについて説明します。
ディープコピーとシャローコピーの定義と、Python言語での実装を取り上げ、この2種類のコピーの違いについて説明します。
私たちが書く多くのプログラムでは、それがどんなに基本的なものであっても、計算効率のような多くの理由のうちの1つのために、リストやオブジェクトをコピーする必要が出てきます。
その方法には、ディープコピーとシャローコピーの2つがあります。
この2つの違いを説明する前に、まず、ディープコピーとシャローコピーとはいったい何なのかを理解しましょう。
Pythonのディープコピー
ディープコピーは、オブジェクトやリスト全体を、独自のメモリアドレスを持つ新しい別のコピーとして作成します。
これが意味するのは、オブジェクトやリストの新しいコピーで行った変更は、元のコピーには反映されないということです。
この処理は、まず新しいリストやオブジェクトを作成し、その後、元のリストから新しいリストへ再帰的に要素をコピーすることで行われます。
簡単に言うと、両方のオブジェクトは互いに完全に独立しているのです。
これは、C++、Java、C#などの言語における値渡しという概念に似ている。
ディープコピー例
Python でディープコピーの概念を実装するために、copy モジュールを使用することにします。
例えば、学生Aの最初の2年間の3教科の成績を含む result_A
というリストのリストがあり、学生Bの成績についても全く同じリストを作りたいとします。
ここでは result_A
リストのディープコピーを作成し、後でそのディープコピーにいくつかの変更を加えて、学生 B の成績を表示することにします。
例 1:
# Program 1 - Deep Copy
import copy
result_A = [[90, 85, 82], [72, 88, 90]] # Student A grades
result_B = copy.deepcopy(result_A) # Student B grades (copied from A)
print(result_A)
print(result_B)
上のスクリプトでは、copy
モジュールの deepcopy
メソッドを使用して、リスト result_A
を result_B
にコピーしています。
次に、両方のリストの内容を画面に表示する。
出力します。
[[90, 85, 82], [72, 88, 90]]
[[90, 85, 82], [72, 88, 90]]
見ての通り、リストは同じものです。
この記事の後半で、これが浅いコピーとどう違うかを見ていきます。
Pythonのシャローコピー
シャローコピーもまた、別の新しいオブジェクトオブジェクトまたはリストを作成しますが、新しいオブジェクトに子要素をコピーする代わりに、単にそれらのメモリアドレスへの参照をコピーします。
したがって、元のオブジェクトに変更を加えると、コピーされたオブジェクトにも反映されますし、その逆もまた然りです。
簡単に言うと、両方のコピーがお互いに依存しているのです。
これは、C++、C#、Javaなどのプログラミング言語における参照渡しの概念に似ている。
シャローコピー例
Python でこれを実装するために、再び copy
モジュールを使いますが、今回はその copy
関数を呼び出します。
浅いコピーの例でも、同じ例のリストを使ってみましょう。
例2:
# Program 2 - Shallow Copy
import copy
result_A = [[90, 85, 82], [72, 88, 90]]
result_B = copy.copy(result_A)
print(result_A)
print(result_B)
上のスクリプトでは、 copy
モジュールの copy
メソッドを使用して、リスト result_A
を result_B
という名前で浅くコピーしています。
次に、両方のリストの内容がコンソールに表示されます。
出力されます。
[[90, 85, 82], [72, 88, 90]]
[[90, 85, 82], [72, 88, 90]]
ここでも、予想通り両者のリストは同じものでした。
次に、 copy
と deepcopy
関数で得られる結果の違いについて説明します。
ディープコピーとシャローコピーの違い
ここまでで、浅いコピーと深いコピーとは何か、そしてなぜコピーを作成するのかについて説明しましたが、次は両者の違いについて説明します。
基本的に、核となる違いは2つだけで、それらは互いにリンクしています。
- ディープコピーはオブジェクトの値のコピーを保存するのに対し、シャローコピーは元のメモリアドレスへの参照を保存する
-
- Deep コピーは、新しい/コピーされたオブジェクトに加えられた変更を元のオブジェクトに反映させないのに対し、Shallow コピーは反映させる
実装に移る前に、このシナリオを想像してほしい。
空のグラスとストローを2本ずつ持っている2人が、飲み物を分け合うとします。
二人はこの飲み物を二つの方法で分け合うことができる。
-
- 片方のグラスに飲み物を入れ、そのグラスにストローを2本とも入れて共有する。
-
- 両方のグラスに飲み物を入れ、それぞれのグラスにストローを1本ずつ挿す。
最初のシナリオは、浅いコピーの場合です。
両方の変数/インスタンスが同じメモリー位置を指し、操作に使用しています。
2つ目のシナリオは、ディープコピーの場合です。
両方の変数/インスタンスは、その操作のために2つの異なるメモリ位置を指している/使用しています。
比較例
この違いを明確にするために、上記の2つの例でこの情報を使って、例1から見ていきましょう。
上では、リスト result_A
を作成し、result_B
という名前の深いコピーを作りました。
ここで、result_B
の内容を変更してみて、result_A
の内容に何らかの影響を与えるかどうかを見てみましょう。
import copy
result_A = [[90, 85, 82], [72, 88, 90]] # Student A grades
result_B = copy.deepcopy(result_A) # Student B grades (copied from A)
# Change first year and first subject's marks to 30
result_B[0][0] = 30
print("Original List: ")
print(result_A)
print("Deep Copy:")
print(result_B)
出力します。
Original List:
[[90, 85, 82], [72, 88, 90]]
Deep Copy:
[[30, 85, 82], [72, 88, 90]]
期待される結果は、元のリストが変更されないということです。
そして、ご覧のように、ディープコピーの変更は元のリストに影響を与えませんでした。
さて、同じことを例2 – 浅いコピーでやってみましょう。
import copy
result_A = [[90, 85, 82], [72, 88, 90]] # Student A grades
result_B = copy.copy(result_A) # Student B grades (copied from A)
# Change first year and first subject's marks to 30
result_B[0][0] = 30
print("Original List: ")
print(result_A)
print("Shallow Copy:")
print(result_B)
出力します。
Original List:
[[30, 85, 82], [72, 88, 90]]
Shallow Copy:
[[30, 85, 82], [72, 88, 90]]
ここで期待される結果は、1回の変更で元のリストとコピーされたリストの両方が変更されることです。
そして、ご覧の通り、浅いコピーに変更を加えると、その変更が元のリストにも反映される結果となりました。
結論
この記事では、シャローコピーとディープコピーとは何か、そして Python 言語で ‘copy’ モジュールを使ってそれらを作成する方法についてお話しました。
コピーモジュールの2つの関数、 copy
と deepcopy
を使って、それぞれシャローコピーとディープコピーを作成しました。
さらに、浅いコピーと深いコピーの2つの違いについて議論し、それらの違いをより良く理解するために、浅いコピーと深いコピーをPythonで実装しました。