この記事では、Python でオブジェクトを深くコピーする方法と浅くコピーする方法について見ていきます。
簡単に言うと、どちらの操作にも copy
モジュールのメソッドを使用することができます。
import copy
shallow_copy_list = copy.copy(original_list)
deepcopy_list = copy.deepcopy(original_list)
ということです。
この章では、これらの用語の意味、Pythonがオブジェクト参照とメモリ内のオブジェクトをどのように扱うか、そしてなぜこれら2つのメソッドがそのように動作するのかを掘り下げて説明します。
Pythonでオブジェクトをシャローコピーする
Pythonで代入文(=
)を使ってリストやクラスインスタンスなど、基本的に他のオブジェクトを含む複合オブジェクトのコピーを作成する場合、Pythonはオブジェクト自体をクローンするわけではありません。
その代わりに、単にターゲットとなるオブジェクトへの参照をバインドするだけです。
次のような要素を含むリストがあるとします。
original_list =[[1,2,3], [4,5,6], ["X", "Y", "Z"]]
このリストを以下のように代入文を使ってコピーしようとします。
shallow_copy_list = original_list
print(shallow_copy_list)
オブジェクトをクローンして、2つ持っているように見えるかもしれません。
[[1,2,3], [4,5,6], ['X', 'Y', 'Z']]
しかし、本当に2つのオブジェクトがあるのでしょうか?いいえ、そうではありません。
メモリ上の同じオブジェクトを指す参照変数が2つあるのです。
これは、メモリ上のオブジェクトのIDを表示することで簡単に確認することができます。
id(original_list) # 4517445712
id(shallow_copy_list) # 4517445712
より具体的な証明は、「2つのリスト」のどちらかの値を変更しようとすることで見ることができます。
それでは、original_list
が指すオブジェクトの最後の要素にアクセスしてみましょう。
# Last element of last element
original_list[-1][-1] = "ZZZ"
print(original_list)
この結果、次のようになります。
[[1, 2, 3], [4, 5, 6], ['X', 'Y', 'ZZZ']]
両方の参照変数が同じオブジェクトを指していることを知っているので、 shallow_copy_list
をプリントしても同じ結果が返されます。
print(shallow_copy_list)
[[1, 2, 3], [4, 5, 6], ['X', 'Y', 'ZZZ']]
>
> シャローコピーとは、オブジェクトへの参照をコピーして、新しい変数に格納する処理です。
original_listと
shallow_copy_listは、メモリ (RAM) 上の同じアドレスを指す単なる参照で、
[[1, 2, 3], [4, 5, 6], [‘X’, ‘Y’, ‘ZZZ’]]` という値が格納されています。
リスト全体のスライスと代入文を使って、オブジェクトの浅いコピーを作成することも可能です。
slice_shallow_copy_list = original_list[:]
浅いコピーを行うもう一つの方法は、Python の標準ライブラリの copy
モジュールを使用することです。
copy` モジュールを使うには、まずインポートする必要があります。
import copy
これで、 copy
モジュールの copy()
メソッドを使うことができます。
second_shallow_copy_list = copy.copy(original_list)
両方印刷して、同じ値を参照しているかどうか確認します。
print(original_list)
print(second_shallow_copy_list)
予想通り、同じ値を参照しています。
[[1, 2, 3], [4, 5, 6], ['X', 'Y', 'ZZZ']]
[[1, 2, 3], [4, 5, 6], ['X', 'Y', 'ZZZ']]
通常、複合オブジェクトをコピーして、例えばメソッドの先頭でクローンを作成し、元のオブジェクトはそのままにしておいて、後でまた使いたいことがあると思います。
これを実現するには、オブジェクトをディープコピーする必要がある。
では、ディープコピーとは何か、複合オブジェクトをディープコピーする方法について説明します。
Pythonでオブジェクトをディープコピーする
Pythonのディープコピーについて説明します。
オブジェクトのディープコピーとは、オブジェクトとその値を、メモリ上の新しいコピー(インスタンス)に、同じ値で本当にコピーすることです。
ディープコピーでは、同じ値への新しい参照を作成するのではなく、元のデータから独立した、同じ値を含む新しいオブジェクトを実際に作成することができるのです。
>
> 一般的なディープコピー処理では、まず、新しいオブジェクトの参照が作成され、次に、すべての子オブジェクトが親オブジェクトに再帰的に追加されます。
この方法では、シャローコピーとは異なり、元のオブジェクトに何らかの変更を加えても、コピーオブジェクトには反映されません(逆もまた然り)。
以下は、典型的なディープコピーの簡単な例です。
Python でオブジェクトをディープコピーするには、 copy
モジュールの deepcopy()
メソッドを使用します。
copy モジュールをインポートして、リストのディープコピーを作成してみましょう。
import copy
original_list = [[1,2,3], [4,5,6], ["X", "Y", "Z"]]
deepcopy_list = copy.deepcopy(original_list)
では、リストを印刷して出力が同じであることを確認し、一意性の証明としてそのIDを表示してみましょう。
print(id(original_list), original_list)
print(id(deepcopy_list), deepcopy_list)
出力は、私たち自身が本物のコピーを作成したことを確認します。
4517599280, [[1, 2, 3], [4, 5, 6], ['X', 'Y', 'Z']]
4517599424, [[1, 2, 3], [4, 5, 6], ['X', 'Y', 'Z']]
では、最後のリストの最後の要素を “O “に変えて、元のリストを変更してみましょう。
そして、その結果を見るために印刷してみましょう。
original_list[-1][-1] = "O"
print(original_list)
期待通りの結果が得られました。
[[1, 2, 3], [4, 5, 6], ['X', 'Y', 'O']]
では、コピーしたリストを印刷してみましょう。
print(deepcopy_list)
前回の修正は、このリストには反映されませんでした。
[[1, 2, 3], [4, 5, 6], ['X', 'Y', 'Z']]
copy()と
deepcopy()`のメソッドは、他の複合オブジェクトにも適用できることを覚えておいてください。
つまり、クラスインスタンスのコピーを作成するためにも使用することができます。
結論
この記事では、オブジェクトを浅くコピーすることと深くコピーすることの意味を学びました。
また、複合オブジェクトの浅いコピーを作成するには copy
モジュールの copy()
メソッドを、深いコピーを作成するには deepcopy()
メソッドを使用することを学びました。