LoginSignup
41
36

More than 5 years have passed since last update.

copyとdeepcopyとnumpy.copyの挙動について

Posted at

ハマったのでメモ.

コピーなし・浅いコピー・深いコピー

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しとくってのは良い選択かもしれないですね.

41
36
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
36