0
0

More than 1 year has passed since last update.

【Python】copyとdeepcopyの違いをサンプルコードで解説:参照先が共有されるかどうかで異なる

Last updated at Posted at 2023-05-28

copydeepcopyの違い

  • 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を使った方が良いケース

  • データ構造が単純である場合
  • 内部のオブジェクトが不変である場合
0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0