ProgrammingPython開発者(ミドルウェア、バックエンド、テスト)

Pythonでコレクションを正しくコピーする方法、copy()が不十分な場合とその理由、deep copyが必要な場合はどのようなものか。

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

回答。

問題の背景:Pythonではオブジェクトのコピー(特にコレクション—リスト、辞書)は重要です。変数に単純に代入すると、新しい参照が作成され、コピーは作成されません。copy()とdeepcopy()の特別なメソッドは、構造の共同使用時に望ましくない「副作用」を避けるために登場しました。

問題:ネストされたコレクション(リストのリスト、辞書の中の辞書)を扱う場合、単純なcopy()はコンテナ自身だけをコピーしますが、内部の要素はコピーしません。これにより、ネストされた要素の変更がすべての「コピー」に反映されるという見落としがちなバグが発生する可能性があります。

解決策:copy.copy()は浅いコピー(shallow copy)を作成します — 新しい上位コンテナを作成しますが、ネストされたオブジェクトは同じままです。copy.deepcopy()はすべてのネストされたオブジェクトを再帰的にコピーします。

コードの例:

import copy lst = [[1, 2], [3, 4]] shallow = copy.copy(lst) deep = copy.deepcopy(lst) lst[0][0] = 10 print(shallow) # [[10, 2], [3, 4]] — ネストされたオブジェクトが変更されました! print(deep) # [[1, 2], [3, 4]] — deepcopyは元の状態を保持しました

主な特徴:

  • 単純な代入は参照のみをコピーし、複製はありません
  • .copy()(またはcopy.copy())は「外側」のコンテナをコピーしますが、ネストされたオブジェクトはコピーしません
  • .deepcopy()はコピーの完全な独立性のために使用されます

ひっかけ質問。

listをコピーするためにlst2 = lst[:]と書くだけで十分ですか?

lst2 = lst[:]はリストの浅いコピーを作成しますが、ネストされたオブジェクト(たとえば、リスト内のリスト)は依然として同じままです。フラットなリストの場合はこれで十分ですが、ネストされた構造の場合はそうではありません。

例:

lst = [[1], [2]] lst2 = lst[:] lst[0][0] = 99 print(lst2) # [[99], [2]] — ネストされた要素が両方の「コピー」で変更されました

すべてのコレクションに対して.copy()は同じように機能しますか?

いいえ。たとえば、dict.copy()は浅いコピーとして機能し、list.copy()はPython 3.3から登場しました。setにはset.copy()があります。ユーザー定義のオブジェクトの場合、.copy()のサポートは実装されたメソッドによります。


どこでもdeepcopyを使うことはできますか?安全で効率的ですか?

いいえ。deepcopyは高価な操作です:大きな構造物に対してはパフォーマンスの問題を引き起こす可能性があり、コピーできないオブジェクトやクロージャー、非標準オブジェクトで壊れることもあります。完全に独立した再帰コピーが本当に必要な場合にのみdeepcopyを使用してください。


一般的なエラーとアンチパターン

  • ネストされた構造のためにdeepcopyの代わりに通常の代入や.copy()を使用する
  • 全てのオブジェクトに対してdeepcopyを適用する(プログラムを遅くする)
  • コピーをサポートしていないオブジェクト(たとえば、ファイル、ストリーム)をコピーしようとする

実例

ネガティブケース

テストでは「辞書の辞書」をdict.copy()を通じてコピーしました。このため、あるユーザーのネストされた構造での修正が突然他のテストに影響を与え(データがグローバルにミュータされました)。

メリット:

  • 速い
  • 簡単

デメリット:

  • 明白ではないバグ、環境の分離の破損

ポジティブケース

copy.deepcopy()を導入し、各オブジェクトのネストレベルと構造の特徴をドキュメントに記述し、部分的な「手動」コピーが可能な大きなボリュームではdeepcopyを避けました。

メリット:

  • 環境の透明性
  • オブジェクトの独立性

デメリット:

  • 構造の理解が必要
  • 大きな構造に対してははるかに多くのメモリとCPU時間を消費します