Pythonには、浅いコピー(shallow copy)、深いコピーの2種類(deep copy)に分かれていることを知っているでしょうか。
本記事では、Pythonの2つのコピーの違いを紹介します。
なお、浅いコピー、深いコピーを理解する上で、ミュータブルとイミュータブルの理解が不可欠です。
そもそも
浅いコピーは、双方の値が連動して、深いコピーは、連動しないということしか理解していませんでした。
浅いコピーは、リストが入れ子になっていると値が連動するのに、そうでないと連動しない理由が分からない。
参照と浅いコピーは、どこが違うのかなど自分で理解が及んでいませんでしたので、まとめました。
浅いコピーとは
あるオブジェクトをコピーし、そのコピーしたオブジェクトを一部変更すると、そのオブジェクトのみならず元のオブジェクトも変更されます。このコピーを浅いコピーといいます。参照とも呼ばれます。
しかし、Pythonの場合、参照と浅いコピーは少し違います。
参照は、idが元のオブジェクトとコピーしたオブジェクトで一致します。
浅いコピーは、idが元のオブジェクトとコピーしたオブジェクトで異なります。
しかし、浅いコピーの中の要素は元のオブジェクトと同じidです。
方法
例として、ミュータブルであるリストで示します。
参照
a=[1,2,3,4]
b=a
print(id(a) == id(b)) #True
3行目は、オブジェクトのidを比較しています。Trueであるため、bは、aのオブジェクトを参照しています。
bは、aと同じオブジェクトであるため、bを変更するとaも変更してしまいます。
aとbは連動してしまいます。
b[0]=2
print(b)
# [2,2,3,4]
print(a)
# [2,2,3,4]
浅いコピー
方法1
import copy
a=[1,2,3,4]
b=copy.copy(a)
print(id(a) == id(b)) #False
方法2
a=[1,2,3,4]
b=a[:]
print(id(a) == id(b)) #False
2つの方法は、同じ動作をします。bはaの浅いコピーになります。
最終行で、オブジェクトのidを比較しています。Falseであるため、bは、aのオブジェクトとは異なるオブジェクトのようです。
aとbは連動しなさそうです。
次に、オブジェクトの要素のidを見てみましょう。
a=[1,2,3,4]
b=a[:] #shallow copy
for i in range(len(a)):
print(id(a[i])==id(b[i]))
#True
#True
#True
#True
Trueであることから、bの要素は、aの要素と同じidが振り分けられています。
ということは、aとbは連動しそうです。
しかし、bの要素は、整数(イミュータブル)であるため、変更する場合、新しいidとして生成されます。
したがって、aとbは連動しません。
b[0]=2
print(b)
# [2,2,3,4]
print(a)
# [1,2,3,4]
次に、リストの中にリスト(ミュータブル)がある場合はどうでしょう。
a=[[1,2],[4,5]]
b=a[:] #shallow copy
for i in range(len(a)):
print(id(a[i])==id(b[i]))
#True
#True
先ほどの例と同様にTrueであることから、bの要素は、aの要素と同じidが振り分けられています。
ということは、bを変更するとaも変更しそうです。
bの要素は、リスト(ミュータブル)であるため、変更する場合、同じidとして更新されます。
したがって、aとbは連動します。
b[0][0]=2
print(b)
# [[2,2],[4,5]]
print(a)
# [[2,2],[4,5]]
深いコピーとは
あるオブジェクトをコピーし、そのコピーしたオブジェクトを一部変更すると、そのオブジェクトは変更されますが、元のオブジェクトは変更されません。このコピーを深いコピーといいます。単純にコピーとも呼ばれます。
深いコピーは、idが元のオブジェクトとコピーしたオブジェクトで異なります。
方法
import copy
a=[1,2,3,4]
b=copy.deepcopy(a)
print(id(a)==id(b)) #Flase
最終行で、オブジェクトのidを比較しています。Falseであるため、bは、aのオブジェクトとは異なるオブジェクトのようです。
では、オブジェクトの要素のidはどうでしょう。
a=[1,2,3,4]
b=copy.deepcopy(a)
for i in range(len(a)):
print(id(a[i])==id(b[i]))
#True
#True
#True
#True
Trueであることから、bの要素は、aの要素と同じidが振り分けられています。
ということは、aとbは連動するのかとなりますが、浅いコピーのときと同様、bの要素は、整数(イミュータブル)であるため、変更する場合、新しいidとして生成されます。
これは、Pythonがわざわざ、イミュータブルなのに新しくidを振り分ける必要がないと認識しているからです。
したがって、aとbは連動しません。
import copy
a=[1,2,3,4]
b=copy.deepcopy(a)
b[0]=2
print(b)
print(a)
次に、リストの中にリスト(ミュータブル)がある場合はどうでしょう。
a=[[1,2],[4,5]]
b=copy.deepcopy(a)
for i in range(len(a)):
print(id(a[i])==id(b[i]))
#False
#False
Falseであることから、aとbの要素は別物です。
したがって、aとbは連動しません。
まとめ
参照は、あるオブジェクトを参照し、その参照したオブジェクトを一部変更すると、そのオブジェクトのみならず元のオブジェクトも変更されます。
浅いコピーは、あるオブジェクトをコピーし、そのコピーしたオブジェクトを一部変更すると、オブジェクト内がミュータブルの場合、そのオブジェクトのみならず元のオブジェクトも変更され、イミュータブルの場合、元のオブジェクトは変更されません。
深いコピーは、あるオブジェクトをコピーし、そのコピーしたオブジェクトを変更すると、元のオブジェクトは変更されません。完全に別物としてコピーします。