akuruhinode's blog

pythonやC#を中心に興味を持った内容について調べています。

Python copy()とdeepcopy()の違い

はじめに

リストや辞書をコピーするときに利用するcopy()関数とdeepcopy()関数の違いをサンプルコードをもとに説明します。

結論から

リストや辞書が入れ子になっている場合、copy()関数では内部のリストや辞書は正しくコピーされません。これに対してdeepcopy()関数では、内部のリストや辞書も正しくコピーされます。
この振る舞いから、copy()関数によるコピーを浅いコピーdeepcopy()関数によるコピーを深いコピーと呼びます。

コピーとは?

ここでいうコピーとは、コピー元と参照先が異なるリストや辞書を作成することを示します。基本的にpythonの変数は参照渡しされますので、単純に変数を別の変数に代入しただけでは正しくコピーされません。

例えば以下のようにリストa、リストbを別々に作成した場合、これらはそれぞれ別の参照先に格納されます。そのためリストaの要素を変更してもリストbは変更されません。

a = ['apple', 'banana']
b = ['apple', 'banana']

a[0] = 'orange' # リストaを変更
print(a)
print(b)
['orange', 'banana']
['apple', 'banana'] 
参照先が異なる場合のイメージ


これに対して、以下のようにリストaをリストbに代入した場合、リストaを変更するとリストbも変更されてしまいます。
これはリストaとリストbの参照先が同じになっていることが原因です。変数の名称が異なるだけで、実際には同じデータを示しているとも言えます。

a = ['apple', 'banana']
b = a

a[0] = 'orange' # リストaを変更
print(a)
print(b)
['orange', 'banana']
['orange', 'banana']
参照先が同じ場合のイメージ

copy()とdeepcopy()の違い

copy()は浅いコピー

まずは、copy()関数で内部にリストを含むリストをコピーしてみます。
copy()関数でも、一見すると内部のリストまでコピーされているように見えます。

import copy
fruits = [['apple', 'banana'], ['orange', 'grape']]
fruits_copy = copy.copy(fruits)

print(fruits)
print(fruits_copy)
[['apple', 'banana'], ['orange', 'grape']]
[['apple', 'banana'], ['orange', 'grape']]

ですが、以下の通り①のようにリストの要素自体を変更した場合はコピー元に反映されませんが、②のように内部のリストの要素を変更した場合はコピー元にもその変更が反映されています。

つまり、リストの要素はコピーされているが、内部のリストの要素は参照渡しされている状態です。これを浅いコピーと言います。

import copy
fruits = [['apple', 'banana'], ['orange', 'grape']]
fruits_copy = copy.copy(fruits)

fruits_copy[0] = ['lemon', 'melon'] # ①:リストの要素を変更
fruits_copy[1][0] = 'pineapple' # ②:リストの内部のリストを変更

print(fruits)
print(fruits_copy)
[['apple', 'banana'], ['pineapple', 'grape']]
[['lemon', 'melon'], ['pineapple', 'grape']]

deepcopy()は深いコピー

deepcopy()関数を利用すれは、内部のリストも正しくコピーされます(コピー先を変更してもコピー元には影響がない)。つまり、内部のリストの要素も異なる参照になります。これを深いコピーと言います。

import copy
fruits = [['apple', 'banana'], ['orange', 'grape']]
fruits_copy = copy.deepcopy(fruits)

fruits_copy[0] = ['lemon', 'melon'] # ①:リストの要素を変更
fruits_copy[1][0] = 'pineapple' # ②:リストの内部のリストを変更

print(fruits)
print(fruits_copy)
[['apple', 'banana'], ['orange', 'grape']]
[['lemon', 'melon'], ['pineapple', 'grape']]