変数への代入(=)、浅いコピー(copy.copy())、深いコピー(copy.deepcopy())に触れながら違いについて解説します。
- 変数への代入 =
- 同じオブジェクトを参照先とする。
- 浅いコピー copy.copy()
- 複合オブジェクト(※)の場合、新しい複合オブジェクトを作って、同じオブジェクトを参照先する。
- 深いコピー copy.deepcopy()
- 複合オブジェクト(※)の場合、新しい複合オブジェクトを作って、再帰的に値をコピーする。
複合オブジェクト:リストの入れ子(ネスト)などのように、階層構造を持つデータ
変数への代入 =
リストを使って考えてみましょう。
まずは、list1をlist2に代入してみます。
>>> list1 = [1, 10, 100, 1000]
>>> list2 = list1
>>> print('list1 : ', list1)
>>> print('list2 : ', list2)
list1 : [1, 10, 100, 1000]
list2 : [1, 10, 100, 1000]
list1[1]を書き換えてみます。その場合、list2はどうなるでしょうか?
>>> list1[1] = 20
>>> print('list1 : ', list1)
>>> print('list2 : ', list2)
list1 : [1, 20, 100, 1000]
list2 : [1, 20, 100, 1000]
list1[1]を書き換えると、list2[1]も同じように書き換わりました。
ということは、list1もlist2も同じオブジェクトを参照していることになります。
同じオブジェクトを参照しているかどうかは、id()関数でオブジェクトIDを確認することが
できます。
>>> print('list1_id : ', id(list1))
>>> print('list2_id : ', id(list2))
list1_id : 4552623880
list2_id : 4552623880
list1とlist2は同じオブジェクトIDを持っていることがわかります。
オブジェクトを箱、オブジェクトIDは箱についた番号、値は箱の中身、変数名は箱についた
タグ、と理解すればわかりやすいでしょうか。
list2 = list1 とは、つまり、”list1″というタグのついた箱に、”list2″というタグをつけることになります。
浅いコピー copy.copy()
新しく参照先を作って値をコピーしたい場合は、copy.copy()を使います。
(リストの場合はlist.copyが使えますが、ここではcopyモジュールと使って解説します。)
先ほどと同じ流れで、=(イコール)の代わりにcopy.copy()を使ってから、list1[1]のみを書き換えてみます。
>>> import copy
>>> list1 = [1, 10, 100, 1000]
>>> list2 = copy.copy(list1)
>>> list1[1] = 20
>>> print('list1 : ', list1, ' / list1_id : ', id(list1))
>>> print('list2 : ', list2, ' / list2_id : ', id(list2))
list1 : [1, 20, 100, 1000] / list1_id : 4550965960
list2 : [1, 10, 100, 1000] / list2_id : 4551592584
copy.copy()を使うことで、list2は新たなオブジェクトとして値のみがコピーされていることがわかります。
深いコピー copy.deepcopy()
リストの入れ子(ネスト)を使って、複合オブジェクトのコピーについて解説します。
まずは、list1をcopy.copy()、copy.deepcopy()でlist2、list3としてコピーしてみます。
>>> import copy
>>> list1 = [[1, 10, 100], [2, 20, 200], [3, 30, 300]]
>>> list2 = copy.copy(list1)
>>> list3 = copy.deepcopy(list2)
>>> print('list1 : ', list1, ' / list1_id : ', id(list1))
>>> print('list2 : ', list2, ' / list2_id : ', id(list2))
>>> print('list3 : ', list3, ' / list3_id : ', id(list3))
list1 : [[1, 10, 100], [2, 20, 200], [3, 30, 300]] / list1_id : 4550963784
list2 : [[1, 10, 100], [2, 20, 200], [3, 30, 300]] / list2_id : 4550965832
list3 : [[1, 10, 100], [2, 20, 200], [3, 30, 300]] / list3_id : 4550965960
オブジェクトIDは全て異なります。
list[1][2] = 200 → 222に書き換えてみると、list2とlist3はどうなるか・・・?
>>> list1[1][2] = 222
>>> print('list1 : ', list1, ' / list1_id : ', id(list1))
>>> print('list2 : ', list2, ' / list2_id : ', id(list2))
>>> print('list3 : ', list3, ' / list3_id : ', id(list3))
list1 : [[1, 10, 100], [2, 20, 222], [3, 30, 300]] / list1_id : 4550963784
list2 : [[1, 10, 100], [2, 20, 222], [3, 30, 300]] / list2_id : 4550965832
list3 : [[1, 10, 100], [2, 20, 200], [3, 30, 300]] / list3_id : 4550965960
list2[1][2]は書き換わりましたが、list3[1][2]は書き換わりませんでした。
何がおきているかを確認するため、入れ子の部分に対してオブジェクトIDを確認してみます。
>>> print('list1[1] : ', list1[1], ' / list1[1]_id : ', id(list1[1]))
>>> print('list2[1] : ', list2[1], ' / list2[1]_id : ', id(list2[1]))
>>> print('list3[1] : ', list3[1], ' / list3[1]_id : ', id(list3[1]))
list1[1] : [2, 20, 222] / list1[1]_id : 4551591752
list2[1] : [2, 20, 222] / list2[1]_id : 4551591752
list3[1] : [2, 20, 200] / list3[1]_id : 4551568008
>>> print('list1[1][2] : ', list1[1][2], ' / list1[1][2]_id : ', id(list1[1][2]))
>>> print('list2[1][2] : ', list2[1][2], ' / list2[1][2]_id : ', id(list2[1][2]))
>>> print('list3[1][2] : ', list3[1][2], ' / list3[1][2]_id : ', id(list3[1][2]))
list1[1][2] : 222 / list1[1][2]_id : 4497097024
list2[1][2] : 222 / list2[1][2]_id : 4497097024
list3[1][2] : 200 / list3[1][2]_id : 4497096320
copy.copy()を使用したlist2は、新しいオブジェクトを作ってはいるが、入れ子部分については元のオブジェクト(list1)と参照先が同じになりました。
よって“浅いコピー”と言えます。
別のオブジェクトとして管理しつつ、値(要素)をコピー間で連動させたい場合は、
“浅いコピー”が向いているようです。
copy.deepcopy()を使用したlist3は、新しいオブジェクトを作り、さらに入れ子部分については再帰的に新しいオブジェクトを作って、値をコピーしていることがわかります。
よって“深いコピー”と言えます。
全く別のオブジェクトとして管理し、値(要素)をコピー間で連動させたくない場合は、
“深いコピー”の方が向いているようです。