Python:浅いコピー(shallow copy)と深いコピー(deep copy)

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は連動しません。

まとめ

参照は、あるオブジェクトを参照し、その参照したオブジェクトを一部変更すると、そのオブジェクトのみならず元のオブジェクトも変更されます。

浅いコピーは、あるオブジェクトをコピーし、そのコピーしたオブジェクトを一部変更すると、オブジェクト内がミュータブルの場合、そのオブジェクトのみならず元のオブジェクトも変更され、イミュータブルの場合、元のオブジェクトは変更されません。

深いコピーは、あるオブジェクトをコピーし、そのコピーしたオブジェクトを変更すると、元のオブジェクトは変更されません。完全に別物としてコピーします。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です