ProgrammingバックエンドPython開発者

浅いコピーと単純な変数の代入の違いは何ですか?ネストされたデータ構造(リストのリスト)におけるcopy.copy()の動作はどうなりますか?

Hintsage AIアシスタントで面接を突破

回答

Pythonにおける変数の代入(例えば、a = b)は、オブジェクト自体をコピーするのではなく、既存のオブジェクトへの新しい名前(参照)を作成します。変更は一方の名前で行われると、もう一方でも見られます(可変オブジェクトの場合)。

浅いコピー(shallow copy)は、copy.copy(obj)やリストのスライス [:] などを用いて作成され、上位レベルの新しいオブジェクトを作成しますが、内部のネストされたオブジェクトは参照によってコピーされます(つまり、両方の構造は同じサブコンテナを「参照」しています)。ネストされたオブジェクトを変更すると、その変更は両方のオブジェクトで見られます。

例:

import copy lst1 = [[1,2], [3,4]] lst2 = copy.copy(lst1) # または lst1[:] lst1[0][0] = 100 print(lst2) # [[100, 2], [3, 4]]

lst2 は新しいリストですが、その最初の要素は同じネストされたリストです。

ひっかけ問題

質問: lst2 = lst1[:] と lst2 = copy.copy(lst1) はどう違いますか?

回答: 実際には、通常の(1階層の)リストの場合、違いはほとんどありません — 両方の方法がリストの浅いコピーを作成します。しかし、ユーザー定義のコンテナクラスの場合は異なる動作をする可能性があります(例えば、自分自身の __copy__ メソッドが実装されている場合)。同様に、他のタイプ(dictsetなど)については、専門のモジュール copy を使用する方が安全です。

import copy lst1 = [1, 2, 3] lst2 = lst1[:] lst3 = copy.copy(lst1) print(lst2 == lst3) # True

このトピックに関する無知からの実際のエラーの例


物語

設定の処理を行うプロジェクトで、開発者はデフォルトのパラメータを params = default_params という代入から複製し、「個別に」変更できると考えていました。結果として、どのコピーでの変更もアプリケーションのすべての部分に連鎖的に影響を及ぼし、実際には同一のオブジェクトを操作していたためです。


物語

未熟なプログラマーはゲームの状態を保持するためにリストの浅いコピーを使用しました(game_states = states[:])。ネストされた構造(フィールド上の形のリスト)では、1つの状態内での変更が他の状態に「流れ込んで」しまい、履歴の巻き戻しや手の繰り返しが壊れてしまいました。


物語

OOPアプリケーションでデータをクローンしようとした際、選択肢はスライスと copy.copy() の間でした。しかし、構造の中で自分のcopyメソッドを持つ独自のクラスが見つかり、copy.copy()を使用したときだけ認識されました。スライスはオブジェクトのコピーのロジックを無視し、予期しないバグが発生しました。