pythonにおけるクラスインスタンスのコピーについて、copyモジュールのcopyとdeepcopyの違いを確認したので、そのまとめと実験の結果です。
結論
pythonのコピーモジュールのcopy.copy
とcopy.deepcopy
について、それぞれ以下の動作をします
copy.copy
- 新たなインスタンスが生成される
- 各インスタンス変数(self.xxx)は、コピー元のインスタンス変数それぞれにcopy.copyを行った値を持つ
- つまり、イミュータブルなオブジェクトであれば値のコピー、ミュータブルなオブジェクトであればコピー元のインスタンスのインスタンス変数への参照を持つクラスインスタンスが生成される
copy.deepcopy
- 新たなインスタンスが生成される
- 各インスタンス変数は、コピー元のインスタンス変数それぞれにcopy.deepcopyを行った値を持つ
- つまり各インスタンス変数は、イミュータブル/ミュータブルに関わらず、コピー元のインスタンスと同じ値を持った新たなオブジェクトとなる
実験
実験内容
以下のコードで実験しました。
クラスを定義した後にディープコピーとシャローコピーを作成し、元のクラスの中身を書き換えます。
Test1クラスはインスタンス変数としてint型1つとlist型2つを持ち、書き換えの際、list1はlistオブジェクト自体を作り直し、list2はオブジェクトの中身だけを書き換えます。
import random
import copy
class Test:
def __init__(self) -> None:
self.val_int = random.randint(0,9)
self.val_list1 = [random.randint(0,9) for _ in range(5)]
self.val_list2 = [random.randint(0,9) for _ in range(5)]
# 値を再設定
def reset(self):
self.val_int = random.randint(0,9)
# list1はリストごと初期化、list2は中身だけ初期化
self.val_list1 = [random.randint(0,9) for _ in range(5)]
for i in range(5):
self.val_list2[i] = random.randint(0,9)
def to_string(self):
print(f'int : {self.val_int}')
print('list1: ', end='')
print(*self.val_list1)
print('list2: ', end='')
print(*self.val_list2)
print()
test = Test()
print('インスタンス初期状態')
test.to_string()
test_copy1 = copy.deepcopy(test)
test_copy2 = copy.copy(test)
test.reset()
print('値リセット後')
test.to_string()
print('リセット後のディープコピー')
test_copy1.to_string()
print('リセット後のシャローコピー')
test_copy2.to_string()
print('各インスタンスのオブジェクトID')
print(f'(test, test_copy1, test_copy2) = ({id(test)}, {id(test_copy1)}, {id(test_copy2)})')
実験結果
結果は以下のとおりです。
ディープコピーをしたインスタンスの各値は初期状態から変化が無く、元のインスタンスへの変更の影響を受けていません。
シャローコピーをしたインスタンスについては、list2が元のインスタンスと同じオブジェクトを参照したままであるため、元のインスタンスへの変更が反映されています。
インスタンス初期状態
int : 0
list1: 0 4 8 5 5
list2: 0 4 9 3 1
値リセット後
int : 6
list1: 4 8 7 2 2
list2: 0 3 6 7 1
リセット後のディープコピー
int : 0
list1: 0 4 8 5 5
list2: 0 4 9 3 1
リセット後のシャローコピー
int : 0
list1: 0 4 8 5 5
list2: 0 3 6 7 1
各インスタンスのオブジェクトID
(test, test_copy1, test_copy2) = (4592619712, 4592627488, 4592623360)