copy
とdeepcopy
の違い
- Pythonでとあるテストコードを書いていたら、テスト対象で使用したJSON形式のパラメータの中身が変わってしまう現象が起きました。原因を追求したら、リストを含むオブジェクトを使うテストコードの関数で
copy
関数を使っていたため、オリジナルのJSONパラメータの中身まで変わってしまっていたことがわかりました。 -
copy
モジュールのcopy.copy
関数とcopy.deepcopy
関数は、Pythonの標準ライブラリで提供されるもので、どちらもオブジェクトのコピーを作成するために使用されます。 - 上記に、「浅いコピー」と「深いコピー」の違いについて記述があります。わかりやすく言うと、「浅いコピー」の方が浅いところまでしかコピーしていませんよ、と言うことです。
- つまり、以下の違いがあります。
- 浅いコピー:オブジェクト自体が複製される&参照しているオブジェクトは共有される
- 深いコピー:オブジェクト自体と参照しているオブジェクトもすべて複製される
サンプルコード(リストの中のリスト)
- 以下がサンプルコードです。
- 浅いコピーの果物には
s
を、深いコピーの果物にはd
を頭につけて複製しています。
- 浅いコピーの果物には
import copy
# Original list with nested list
fruits = [['apple', 'banana'], ['orange', 'grape']]
# Shallow copy using copy.copy
shallow_copy = copy.copy(fruits)
# Deep copy using copy.deepcopy
deep_copy = copy.deepcopy(fruits)
# Modifying the copies
shallow_copy[0] = ['s_lemon', 's_melon']
shallow_copy[1][0] = 's_pineapple'
shallow_copy[1].append('s_mango')
deep_copy[0] = ['d_lemon', 'd_melon']
deep_copy[1][0] = 'd_pineapple'
deep_copy[1].append('d_mango')
# Printing the original list and its copies
print("Original list:")
print(fruits)
print("Shallow copy:")
print(shallow_copy)
print("Deep copy:")
print(deep_copy)
- 結果:
Original list:
[['apple', 'banana'], ['s_pineapple', 'grape', 's_mango']]
Shallow copy:
[['s_lemon', 's_melon'], ['s_pineapple', 'grape', 's_mango']]
Deep copy:
[['d_lemon', 'd_melon'], ['d_pineapple', 'grape', 'd_mango']]
- 結果からわかるように、
Original list
に影響を与えているのは、頭にs
がついている浅いコピーの方だけです。深いコピーはOriginal list
に影響を与えていないことがわかります。
結論:浅いコピー「内部のリストの要素は参照コピー」
- 浅いコピーはなんでもオリジナルに影響を与えるのか、というと勿論そうではありません。上記の例では
s_pineapple
はオリジナルリストに反映されていますが、['s_lemon', 's_melon']
は反映されていませんよね。これは、['s_lemon', 's_melon']
の代入の場合は、新しいリストオブジェクトが作成され、shallow_copy[0]
がそれを参照するためです。一方、shallow_copy[1][0] = 's_pineapple'
の変更が元のリストに影響を及ぼすのは、内部のリストオブジェクトへの参照が共有されているためです。 - 一言で言えば、浅いところ(リストの要素)まではコピーするけれど、深いところ(内部のリストの要素)は参照コピー(オブジェクトIDのコピー)なんじゃ、というのが「浅いコピー」と言うことですね。「深いコピー」の場合は、内部のリストの要素も複製されるので、上記のように変更があった場合でもオリジナルリストに影響を与えません。
- 「参照渡し」という表現は好ましくないという旨を @shiracamus さんにコメントいただき、「参照をコピーする」「オブジェクトIDをコピーする」という表現に修正しました。ご指摘ありがとうございます。
- 上記はリストの例ですが、リストのリスト(多次元リスト)だけではなく、オブジェクトを含むオブジェクト構造や辞書を含むネスト構造の辞書の場合にこの違いが見られるようになります。
サンプルコード(辞書の中の辞書)
- 自分が遭遇したJSON形式でも「辞書の中の辞書」のような構造になっていると同様です。
import json
import copy
# JSON形式のデータ
json_data = '''
{
"name": "John",
"age": 30,
"address": {
"street": "123 Main St",
"city": "New York"
}
}
'''
# JSONデータをPythonの辞書に変換
data_dict = json.loads(json_data)
print("元のデータ1:")
print(data_dict)
# deepcopyを使用してディープコピーを作成
data_copy = copy.deepcopy(data_dict)
print("\nディープコピー1:")
print(data_copy)
# 元のデータを変更&出力
data_dict["age"] = 35
data_dict["address"]["city"] = "San Francisco"
print("\n元のデータ2:")
print(data_dict)
print("\nディープコピー2:")
print(data_copy)
- 結果
元のデータ1:
{'name': 'John', 'age': 30, 'address': {'street': '123 Main St', 'city': 'New York'}}
ディープコピー1:
{'name': 'John', 'age': 30, 'address': {'street': '123 Main St', 'city': 'New York'}}
元のデータ2:
{'name': 'John', 'age': 35, 'address': {'street': '123 Main St', 'city': 'San Francisco'}}
ディープコピー2:
{'name': 'John', 'age': 30, 'address': {'street': '123 Main St', 'city': 'New York'}}
- 結果を見てわかるように、
deepcopy
を利用すると元のデータの浅いところと深いところどちらを変更しても、ディープコピーされた方には何も影響がないことがわかります。 - (普通の
copy
を使うと、ディープコピー2
の'New York'
は'San Francisco'
になります)
どちらを使うべき?
- どちらを使うべきかはそれぞれのユースケースで検討するのが良いと思います。ディープコピーは再帰的なコピー操作を行うため、コピー元のオブジェクトが循環参照を含む場合や、非常に大きなオブジェクトをコピーする場合には時間とメモリの面でコストが高くなる可能性があります。
- 今回、自分が実装していたテストコードでは、テスト対象のパラメータにJsonファイルを使用していました。Jsonの場合、辞書の中の辞書、みたいなことが起きやすいので、Jsonファイルを使ったテストコードの時は
deepcopy
の観点が大切だなと感じました。
deepcopy
を使った方が良いケース
- 可変なデータ構造をコピーする場合
- データ構造が多次元のネストを含む場合
- オブジェクトと参照の独立性を維持することが重要な場合
copy
を使った方が良いケース
- データ構造が単純である場合
- 内部のオブジェクトが不変である場合