浅いコピーではもう安心できない!Pythonで`copy.deepcopy()`を使ってオブジェクトを安全にコピーする方法


一方、通常の代入や copy.copy() 関数を使用した浅いコピーでは、ネストされたオブジェクトへの参照のみがコピーされます。つまり、元のオブジェクトを変更すると、そのコピーにも影響が及ぶ可能性があります。

copy.deepcopy() は、以下の状況で特に有用です。

  • オブジェクトを永続化する必要がある場合
  • オブジェクトを安全に共有する必要がある場合
  • オブジェクトを独立して操作する必要がある場合

copy.deepcopy() のしくみ

copy.deepcopy() は、再帰アルゴリズムを使用して、元のオブジェクトとそのすべてのネストされた内容を新しいオブジェクトにコピーします。このアルゴリズムは、以下のステップで実行されます。

  1. 新しいオブジェクトを作成します。
  2. 元のオブジェクトの各属性を反復処理します。
  3. 属性がプリミティブ型(数値、文字列、ブール値など)の場合は、その値を新しいオブジェクトにコピーします。
  4. 属性がコンテナ型(リスト、タプル、辞書など)の場合は、再帰的に新しいコンテナ型を作成し、元のコンテナ型の各要素を新しいコンテナ型にコピーします。
  5. 属性がカスタムオブジェクトの場合は、copy.deepcopy() を使用してそのオブジェクトのコピーを作成し、新しいオブジェクトにそのコピーを格納します。

copy.deepcopy() の例

以下の例では、copy.deepcopy() を使用してリストと辞書のコピーを作成する方法を示します。

import copy

original_list = [1, 2, 3]
original_dict = {'a': 1, 'b': 2}

list_copy = copy.deepcopy(original_list)
dict_copy = copy.deepcopy(original_dict)

print(original_list == list_copy)  # True
print(original_dict == dict_copy)  # True

original_list[0] = 10
original_dict['a'] = -10

print(original_list == list_copy)  # False
print(original_dict == dict_copy)  # False

この例では、copy.deepcopy() を使用して original_listoriginal_dict の完全なコピーが作成されます。その後、元のリストと辞書を変更しても、コピーは変更されません。

  • 大規模な複雑なオブジェクトをコピーする場合は、copy.deepcopy() の代わりに専用のライブラリを使用することを検討してください。
  • copy.deepcopy() は、循環参照(オブジェクトが自身を参照している場合)を処理できません。このような場合は、エラーが発生する可能性があります。
  • copy.deepcopy() は、再帰アルゴリズムを使用してオブジェクトをコピーするため、非常に時間がかかる場合があります。


例 1: ネストされたリストのコピー

この例では、ネストされたリスト構造を持つオブジェクトをコピーする方法を示します。

import copy

original_list = [[1, 2], [3, 4]]
list_copy = copy.deepcopy(original_list)

original_list[0][0] = 10
print(original_list)  # [[10, 2], [3, 4]]
print(list_copy)  # [[1, 2], [3, 4]]

例 2: 辞書とリストを含むオブジェクトのコピー

この例では、辞書とリストを含むオブジェクトをコピーする方法を示します。

import copy

original_obj = {'data': [1, 2, 3], 'info': {'name': 'Alice', 'age': 30}}
obj_copy = copy.deepcopy(original_obj)

original_obj['data'][0] = 10
original_obj['info']['age'] = 25

print(original_obj)  # {'data': [10, 2, 3], 'info': {'name': 'Alice', 'age': 25}}
print(obj_copy)  # {'data': [1, 2, 3], 'info': {'name': 'Alice', 'age': 30}}

例 3: カスタムオブジェクトのコピー

この例では、カスタムオブジェクトをコピーする方法を示します。

import copy

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

original_person = Person('Bob', 40)
person_copy = copy.deepcopy(original_person)

original_person.age = 50
print(original_person.age)  # 50
print(person_copy.age)  # 40

これらの例は、copy.deepcopy() を使用してさまざまな種類のオブジェクトをコピーする方法を示すほんの一例です。

  • copy.deepcopy() は、循環参照(オブジェクトが自身を参照している場合)を処理できません。このような場合は、エラーが発生する可能性があります。循環参照を処理する必要がある場合は、専用のライブラリを使用する必要があります。
  • copy.deepcopy() は、再帰アルゴリズムを使用してオブジェクトをコピーするため、非常に時間がかかる場合があります。パフォーマンスが重要な場合は、copy.copy() などの代替ソリューションを検討してください。


浅いコピー (copy.copy() と = 演算子)

浅いコピーは、元のオブジェクトの参照を新しいオブジェクトにコピーします。ネストされたオブジェクトはコピーされず、元のオブジェクトと同じオブジェクトを参照します。これは、単純なデータ型(数値、文字列、ブール値など)のコピーや、オブジェクトの独立したコピーが必要ない場合に適しています。

import copy

original_list = [1, 2, 3]
shallow_copy = copy.copy(original_list)
equal_shallow_copy = original_list

original_list[0] = 10
print(original_list)  # [10, 2, 3]
print(shallow_copy)  # [10, 2, 3]
print(equal_shallow_copy)  # [10, 2, 3]

構造化データのコピーのためのライブラリ

特定の種類の構造化データ(リスト、辞書、セットなど)をコピーする場合は、専用のライブラリを使用する方が効率的である場合があります。以下に、いくつかの例を示します。

カスタムコピーロジック

複雑なカスタムオブジェクトの場合は、copy.deepcopy() の代わりに、独自のロジックを使用してオブジェクトをコピーすることがあります。これにより、よりきめ細かい制御とパフォーマンスの向上が得られます。

シリアライズと復元

オブジェクトを永続化またはネットワーク経由で送信する必要がある場合は、シリアライズと復元という方法もあります。これには、JSON、pickle、または独自のシリアライゼーション形式を使用できます。

import json

original_obj = {'name': 'Alice', 'age': 30, 'hobbies': ['reading', 'coding']}

json_data = json.dumps(original_obj)
copy_obj = json.loads(json_data)

print(original_obj == copy_obj)  # True

最適な代替方法の選択

使用する代替方法は、以下の要因によって異なります。

  • 個人的な好み
  • オブジェクトの使用方法
  • パフォーマンス要件
  • コピーするオブジェクトの種類と構造

各オプションの長所と短所を比較検討し、状況に合ったものを選択することが重要です。