Pythonのディープコピーとシャローコピーの違いを解説する

スポンサーリンク

今回は、PythonのCopyモジュールを使って、ディープコピーとシャローコピーの操作を行う方法を紹介します。

さて、ディープコピーとシャローコピーとはどういう意味でしょうか?

例題を使って見てみましょう。

スポンサーリンク

Why do we need Python Copy module?

Pythonでは、すべてがオブジェクトで表現されます。

そのため、多くの場合、オブジェクトを直接コピーする必要があるかもしれません。

このような場合、代入演算子を直接使用することはできません。

代入のポイントは、複数の変数が同じオブジェクトを指すことができることです。

つまり、それらの変数のいずれかを使ってオブジェクトを変更すると、変更がいたるところに反映されてしまうのです。

次の例は、この問題を、ミュータブルである共有リストオブジェクトを使って説明しています。

a = [1, 2, 3, 4]
 
b = a
 
print(a)
print(b)
 
b.append(5)
 
# Changes will be reflected in a too!
print(a)
print(b)

結果は以下の通りです。

[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]

見ての通り、両方の変数が同じオブジェクトを指しているので、 b が変更されると a も変更されます!

この問題に対処するために、PythonはCopyモジュールを使って方法を提供しています。

PythonのCopyモジュールは標準ライブラリの一部であり、以下の記述でインポートすることができます。

import copy

このモジュールでは、主に2種類の操作を実行することができます。

  • シャローコピー
  • ディープコピー

では、これらのメソッドを見てみましょう。

シャローコピー

このメソッドは、シャローコピー操作を実行するために使用されます。

このメソッドを呼び出すための構文は次のとおりです。

import copy
 
new_obj = copy.copy(old_obj) # Perform a shallow copy

このメソッドは次の2つのことを行います。

  • 新しいオブジェクトを作成する
  • 元のオブジェクトの中にあるオブジェクトの参照をすべて挿入します。

さて、これは新しいオブジェクトを作成するので、新しいオブジェクトが古いオブジェクトと異なることを確認することができます。

しかし、ネストされたオブジェクトへの参照は維持されます。

つまり、コピーするオブジェクトが他の変更可能なオブジェクト(リスト、セットなど)を持っている場合、これはまだ同じネストされたオブジェクトへの参照を維持するのです!このことを理解するために、次のようなオブジェクトを考えてみましょう。

これを理解するために、例を挙げてみましょう。

最初の点を説明するために、単純な整数のリストで試してみましょう(ネストされたオブジェクトはありません!)。

import copy
 
old_list = [1, 2, 3]
 
print(old_list)
 
new_list = copy.copy(old_list)
 
# Let's try changing new_list
new_list.append(4)
 
# Changes will not be reflected in the original list, since the objects are different
print(old_list)
print(new_list)

結果は以下の通りです。

出力

[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]

見ての通り、オブジェクトが単純なリストであれば、浅いコピーで問題はありません。

別のケースとして、オブジェクトがリストのリストである場合を考えてみましょう。

import copy
 
old_list = [[1, 2], [1, 2, 3]]
 
print(old_list)
 
new_list = copy.copy(old_list)
 
# Let's try changing a nested object inside the list
new_list[1].append(4)
 
# Changes will be reflected in the original list, since the object contains a nested object
print(old_list)
print(new_list)

結果は以下の通りです。

[[1, 2], [1, 2, 3]]
[[1, 2], [1, 2, 3, 4]]
[[1, 2], [1, 2, 3, 4]]

ここで、 old_listnew_list の両方が影響を受けていることに注意してください!

このような振る舞いを避けたい場合は、すべてのオブジェクトをネストしたオブジェクトも含めて再帰的にコピーする必要があります。

これは、Python の copy モジュールを使った Deep Copy 操作と呼ばれます。

ディープコピー

このメソッドはシャローコピーメソッドと似ていますが、今度は元のオブジェクトから(ネストしたオブジェクトも含めて)すべてを新しいオブジェクトにコピーします。

ディープコピー操作を行うには、以下の構文を使用します。

import copy
 
new_object = copy.deepcopy(old_object)

では、古い例を使って、ディープコピーで問題を解決してみましょう。

import copy
 
old_list = [[1, 2], [1, 2, 3]]
 
print(old_list)
 
new_list = copy.deepcopy(old_list)
 
# Let's try changing a nested object inside the list
new_list[1].append(4)
 
# Changes will be reflected in the original list, since the objects are different
print(old_list)
print(new_list)

結果は以下の通りです。

[[1, 2], [1, 2, 3]]
[[1, 2], [1, 2, 3]]
[[1, 2], [1, 2, 3, 4]]

古いリストが変化していないことに注意してください。

しかし、すべてのオブジェクトをコピーするため、このディープコピーメソッドはシャローコピーメソッドと比較して少しコストがかかります。

だから、必要なときだけ、賢くこれを使おう!

まとめ

この記事では、PythonのCopyモジュールを使用して、浅いコピーと深いコピー操作を行うことについて学びました。

参考文献

  • Python Copy Module ドキュメント
  • Python コピーモジュールに関する JournalDev の記事
タイトルとURLをコピーしました