ハマったのでメモ.
コピーなし・浅いコピー・深いコピー
pythonでlistを別の変数に代入すると,「参照渡し」になる.
x = [1, 2, 3]
y = x
y[0] = 999
x # yへの変更がxにも影響を及ぼした.
>>> [999, 2, 3]
これを回避 (= 値渡し) するためには,copyを使うと良い.
from copy import copy
x = [1, 2, 3]
y = copy(x) # x[:]でも良い (むしろこっちのほうがシンプル)
y[0] = 999
x # yへの変更が影響しなかった
>>> [1, 2, 3]
ではcopyは完全に行われたのか,というと,そうではない.
例えば,入れ子にされたlistについては参照渡しになっている.
from copy import copy
x = [[1, 2, 3], 4, 5]
y = copy(x)
y[0][0] = 999
x # copyしたハズなのに!
>>> [[999, 2, 3], 4, 5]
そこで,入れ子等を含め何もかもを完全にcopyしたい場合はdeepcopyを使う.
from copy import deepcopy
x = [[1, 2, 3], 4, 5]
y = deepcopy(x)
y[0][0] = 999
x
>>> [[1, 2, 3], 4, 5]
NumPyはどうなのか.
試してみたところ,どうやら浅いコピーと深いコピーのどちらを実行するか自動で判断されるようである.
以下の2つは,x内の2番目のリストに6が含まれるかどうかだけが異なる.
import numpy as np
x = [[1, 2, 3], [4, 5]]
y = np.copy(x)
y[0][0] = 999
x
>>> [[999, 2, 3], [4, 5, 6]]
import numpy as np
x = [[1, 2, 3], [4, 5, 6]]
y = np.copy(x)
y[0][0] = 999
x
>>> [[1, 2, 3], [4, 5, 6]]
どうも,n次元配列に直せそうなものに対しては深いコピーを実施するようである.
行列がlistの入れ子で表現される以上まっとうな考えかただと思う.
ちなみに3次元でも同様である.
import numpy as np
x = [[[1, 0], [2, 0], [3]],
[[4, 0], [5, 0], [6, 0]]]
y = np.copy(x)
y[0][0][0] = 999
x
>>> [[[999, 0], [2, 0], [3]], [[4, 0], [5, 0], [6, 0]]]
import numpy as np
x = [[[1, 0], [2, 0], [3, 0]],
[[4, 0], [5, 0], [6, 0]]]
y = np.copy(x)
y[0][0][0] = 999
x
>>> [[[1, 0], [2, 0], [3, 0]], [[4, 0], [5, 0], [6, 0]]]
感想
deepcopy最強!!!!!
(一応) 公式曰く,深いコピー操作には2つの問題が有るとのこと.
- 再起的なオブジェクトをコピーするときに無限ループしてしまう
- 共有すべき管理データを余分にコピーしてしまう
でも個人で扱うぶんには多少冗長なコピーがあっても痛くない.
再帰処理だってそう頻繁に必要とされるものじゃないし,とりあえずハマりそうなシチュエーションではdeepcopyしとくってのは良い選択かもしれないですね.