Pythonの浅いコピー、深いコピー

2ヶ月おきに更新されるblogです。

今回は「浅いコピー(shallow copy)と深いコピー(deep copy)」について。
Pythonに限った話ではありませんが。

たくさんの方が同様の記事を書かれています。
プログラミング初心者はよく引っかかるそうですが、もれなく私も引っかかりました。
2015年の6月頃、曲線の二重接線を求めるスクリプトを書いていた頃の話です。はい、今更です。

参照

あるリストAを複製してリストBとし、リストBの中身をいじろうとしたのですが、リストBをいじるとリストAまで変更されてしまう!なんでや!

結果:[3,2,1] [3,2,1]

…えー、コピーするどころかただ参照しているだけですね。
2行目でBにAを代入しましたが、これではAとBが同じオブジェクト[1,2,3]を参照しています。
同じ[1,2,3]をreverse()でひっくり返したのだから、AもBも[3,2,1]になってしまいます。

例えるなら、「机の上に置いてあるスマホを始めはAさんだけが見ていて、途中でBさんがやって来たので今はAさんと一緒に見ている」ような状況でしょうか。Bさんが見ているスマホを上下逆さまにすれば、もちろんAさんが見ているスマホも逆さまになります。

浅いコピー

このトラブルを解決すべく私はネットで検索…せず、当時の自分の浅い知識の範囲内でなんとかしました!どあほ!

結果:[1,2,3] [3,2,1]

思い通りの結果を得ることができました。
でもこれ、もっと簡単に書けるんですよね。

結果は上と同様です。
2行目で “スライス” と呼ばれる操作を用いています。スライスについては詳しく説明しません。
Aの要素1つ1つをBに放り込んでいるわけです。

例えるなら、「机の上に置いてあるAさんのスマホと全く同じ状態のスマホをもう1台用意してBさんのものとする」といった所でしょうか。Bさんのスマホをひっくり返してもAさんのスマホはそのままです。

ちなみに、スライスを使うならさらに簡単に書くことができます。

Bを作るときにもう逆順にして放り込んでいくわけです。

浅いコピーの問題

これで当初の目的は達成されたわけですが、浅いコピーは何もかもコピーするわけではありません。
リストの中身が何かを参照している時、その参照先自体はコピーされないのです。

リストp,q,rをリストAに放り込みます。

結果:[[0, 2, 3], [4, 5, 6], [7, 8, 9]] [[0, 2, 3], [4, 5, 6], [7, 8, 9]]

リストAの0番目の0番目、つまりリストpの0番目、つまり “1” を “0” に変えたのですが、見事にリストBの “1” も “0” に変わっています。
リストAの中のリストpの要素はリストBの中のリストpの要素と同一のものを見ているのです。

また例えるなら、AさんとBさんはそれぞれ自分のスマホで画像を見ているが、その画像ファイルはDropboxのようなクラウド上にある同一ファイルである、ような状況でしょうか。片方のスマホをひっくり返した所でもう片方には何の影響もありませんが、画像を加工するなどして弄ってしまうともう片方のスマホからも同様に加工されたものが見えます。

深いコピー

さて、ようやく深いコピーの話です。
上記の浅いコピーの問題を解決するための方法がこちらです。

結果:[[0, 2, 3], [4, 5, 6], [7, 8, 9]] [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

リストAの “1” は “0” になり、リストCの “1” はそのままです。

例えるなら、AさんとBさんはそれぞれクラウドから端末上にダウンロードしてきた画像を見ているような状況です。写真にどんな加工をしてもお互いの写真には関係ありません。

まとめ

長くなりましたが、コピーにも色々あるということを知っておかないと “正体不明” のバグに悩まされることになりそうです。

  • 参照
    B=A
    同一のリストを参照している
  • 浅いコピー
    B=A[:]
    別のリストを作成、しかしリストの中身が参照するものは同一
  • 深いコピー
    B=deepcopy(A) <deepcopyをimportする必要あり>
    リストの中身が参照するものもコピー

ではまた。

「Pythonの浅いコピー、深いコピー」への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です