#copy.copyとcopy.deepcopy
##1. pythonにおけるcopy
pythonのlist型やdict型のコピーは少しややこしい。一般的に、これらのオブジェクトのコピーとしては,
- =を用いた参照の値渡し
- copy.copy()を用いた浅いコピー
- copy.deepcopy()を用いた深いコピー
の3つがある。理解した範囲で結論から書くと、
=は単なる参照の値渡しで、copy.copy()は一番上層のオブジェクトだけを新しく作成して中のオブジェクトは参照の値渡し、copy.deepcopyは最下層まで、全てのobjectが新たに作られて渡される。
ということだと思う。
実際に挙動の詳細を2次元配列を用いて確認する。
まず、hoge = [[1,2,3],[4,5,6]]という2次元配列を上記の3つの手法でコピーし、id()でオブジェクトの場所を確認すると、
>>> hoge = [[1,2,3],[4,5,6]]
>>> fuga = hoge
>>> piyo = copy.copy(hoge)
>>> hogera = copy.deepcopy(hoge)
>>> id(hoge) == id(fuga)
True
>>> id(hoge) == id(piyo)
False
>>> id(hoge) == id(hogera)
False
となる。=でコピーされたfugaはhogeと同じ場所にあるオブジェクトを参照しているのに対し、copy.copy()及び、copy.deepcopy)()でコピーされたpiyoとhogeraそのものは異なる場所にあるオブジェクトを参照していることがわかる。
中の要素の場所はどうなってるかというと
>>> id(hoge[0]) == id(piyo[0])
True
>>> id(hoge[0]) == id(hogera[0])
False
となり、copy.copy()でコピーされたpiyoの要素の参照先は、hogeと同じになっているのに対して、hogeraでは異なる。つまり、中の要素においても新しいlistオブジェクトが生成されたことがわかる。
##2. 一部の要素を共有した2次元配列
挙動の違いは分かったが、実際どんなときにcopy.copyとcopy.deepcopyを使い分けるのか。全部deepcopyにしとけばいいのではないか。ということで無理やりcopy.copyを使う例を考えてみた。コードはさっきまでの続き
>>> piyo.append([7,9,11])
>>> hoge.append([8,10,12])
>>> piyo
[[1, 2, 3], [4, 5, 6], [7, 9, 11]]
>>> hoge
[[1, 2, 3], [4, 5, 6], [8, 10, 12]]
>>> id(hoge[0]) == id(piyo[0])
True
>>> id(hoge[1]) == id(piyo[1])
True
>>> id(hoge[2]) == id(piyo[2])
False
>>> hoge[0][1] = 20 #hogeの0番目のlistの2番目の要素を20にする。
>>> hoge
[[1, 20, 3], [4, 5, 6], [8, 10, 12]]
>>> piyo
[[1, 20, 3], [4, 5, 6], [7, 9, 11]] #piyoの0番目のlistの2番目の要素も変わる。
>>> hoge[2][1] = 20 #hogeの2番目のlistの2番目の要素も20にする。
>>> hoge
[[1, 20, 3], [4, 5, 6], [8, 20, 12]]
>>> piyo
[[1, 20, 3], [4, 5, 6], [7, 9, 11]] #piyoには影響しない。
上の挙動からもわかるが、このときhogeとpiyoの間では、0番目の1番目の要素(list)の情報は共有されている(参照先が同じになっている)が、2番目以降の要素(list)は異なるという状態になっている。つまり、一部の要素だけ共有した2次元配列を作成できている。こんな風に、一部の要素だけ共有するような多次元listもしくはdictをつくるときは、最初に共通する部分だけを持ったオブジェクトをつくって、copy.copy()でコピーするのがいいのかもしれない。メモリの節約にもなる。
そんなことするくらなら、クラスつくって、クラス変数とインスタンス変数で分ければいい気もするけど、、