ハマったのでメモ残します。。
Pythonで二次元配列のリストなどをコピーする場合、浅いコピー(shallow copy)と、深いコピー(deep copy)の使い分けを考える必要があります。
具体的にはcopy()メソッドや、copyモジュールにあるcopy()関数のdeepcopy()関数を使うパターンがあります。(他にもあるかも)
今回ハマったのがコレ。
実装内容
import copy l = [[j for j in range(1, 4)] for i in range(1, 4)] l2 = l.copy() l3 = copy.deepcopy(l) print(l) print(l2) # print(l3) l2[0][0] = 99 # l3[0][0] = 88 print(l) print(l2) # print(l3)
出力結果
[[1, 2, 3], [1, 2, 3], [1, 2, 3]] [[1, 2, 3], [1, 2, 3], [1, 2, 3]] [[99, 2, 3], [1, 2, 3], [1, 2, 3]] [[99, 2, 3], [1, 2, 3], [1, 2, 3]]
二次元配列のリスト「l」を生成して、2パターンのコピーを生成します。
「l2」は浅いコピー。「l3」が深いコピーです。
上記では、「l3」をコメントアウトして消しています。
「l」の浅いコピー「l2」を生成した後に「l2」の先頭にある要素を「1」→「99」に更新。
すると、コピー元である「l」の先頭にある要素も更新されている事が確認できます。
これは浅いコピー「l2」が参照データを取得しているという事を意味しています。
参照データを更新しちゃっているので、参照元である「l」の先頭要素を更新するという結果になります。
結果的に「l2」の先頭要素も更新できるのですが、わざわざコピーしてるので問題が発生するのが普通です。
次は「l2」をコメントアウトして「l3」のコメントアウトを解除します。
実装内容
import copy l = [[j for j in range(1, 4)] for i in range(1, 4)] l2 = l.copy() l3 = copy.deepcopy(l) print(l) # print(l2) print(l3) # l2[0][0] = 99 l3[0][0] = 88 print(l) # print(l2) print(l3)
出力結果
[[1, 2, 3], [1, 2, 3], [1, 2, 3]] [[1, 2, 3], [1, 2, 3], [1, 2, 3]] [[1, 2, 3], [1, 2, 3], [1, 2, 3]] [[88, 2, 3], [1, 2, 3], [1, 2, 3]]
今回は「l3」の先頭にある要素を更新していますが、コピー元である「l」の先頭の要素に変化がありません。
これは深いコピーが「l」から「l3」に値渡しをしているので、どれだけ「l3」の要素に変更を加えても「l」には影響が出ないという事です。
一次元配列だと、このような違いを意識せずとも狙った結果が出せてしまうので、たまにこういった実装をすると痛い目に合います。
っていうか私は食らいました。。
確か浅いコピーと深いコピーの話はPythonエンジニア認定試験に出てたんですよね。。
やはり「試験勉強した事がある」というレベルでは実際に実装する時に全く使い物にならないですね。
もっともっと書く量を増やさなきゃいけません。