概要(ディープコピーは「入れ物も中身も全部“別物”にする」コピー)
copy.deepcopy は、リストや辞書などの入れ子構造を含むオブジェクトを「最上位から末端まで」再帰的にコピーして、完全に独立した複製を作ります。代入「=」は参照共有、シャローコピーは外側だけ独立、ディープコピーは内側まで独立。この違いを体で覚えると、予期せぬ連動バグを防げます。
import copy
a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)
b[0].append(99)
print(a) # [[1, 2], [3, 4]](影響なし)
print(b) # [[1, 2, 99], [3, 4]]
Python基本の使い方(ここが重要)
ディープコピーの基本形と「=/シャロー」との違い
import copy
a = [[1], [2]]
x = a # 代入(参照共有)
y = a.copy() # シャローコピー(外側だけ別)
z = copy.deepcopy(a) # ディープコピー(内側まで別)
y[0].append(9)
z[1].append(8)
print(a) # [[1, 9], [2]] ← y の変更が波及(内側共有)
print(z) # [[1], [2, 8]] ← 完全独立
Pythonイミュータブルは“見かけ上”問題になりにくい
数値・文字列・タプルなどは中身を変更できないため、シャローでも連動問題が表面化しにくい一方、辞書やリストなどミュータブル要素が入っていると連動します。構造の“どこがミュータブルか”を把握して使い分けましょう。
import copy
a = {"name": "hanako", "tags": ["coffee", "tea"]}
b = copy.deepcopy(a)
b["tags"].append("sugar")
print(a["tags"]) # ['coffee', 'tea'](影響なし)
Python重要ポイントの深掘り(再帰コピーの仕組み・性能・制約)
deepcopy の再帰とメモ(循環参照にも対応)
deepcopy はオブジェクトを辿りながら再帰的にコピーします。循環参照(自分を参照する構造)があっても内部の「メモ」表で辿ったオブジェクトを覚えているため、無限ループになりません。
import copy
a = []
a.append(a) # a 自身を要素に持つ(循環)
b = copy.deepcopy(a)
print(b is b[0]) # True(循環構造が保たれつつ“別物”)
Pythonパフォーマンスの勘所(重いので“必要最小限”に)
- コスト感: 構造のサイズ・深さに比例して時間とメモリが増える。
- 指針: 変更が及ぶ可能性がある“必要な階層のみ”を手動コピー(防御的コピー)する方が軽く済むケースが多い。完全分離が必須なら deepcopy。
# 例:一次入れ子だけ独立させる(軽量防御)
a = [[1, 2], [3, 4]]
b = [row[:] for row in a] # ここだけ守る
Pythonコピーできない/すべきでないもの(制約)
- ファイルハンドル・ソケット・スレッドロック: “そのもの”を複製しても意味がなく、例外や不正な状態になり得る。参照を保持し直す/ハンドルを再作成する設計にする。
- 関数やクラスオブジェクト: 参照だけで十分なことが多い。必要に応じて設定データだけ deepcopy する。
ユーザー定義クラスの制御(deepcopy と memo)
自作クラスで「どうコピーされたいか」を定義できます。memo は循環回避用の辞書。ここで“必要部分だけ再生成”や“共有してよい部分はそのまま”など、意図をコード化できます。
import copy
class Config:
def __init__(self, path, cache):
self.path = list(path)
self.cache = cache # 重い共有資源は共有のままにする例
def __deepcopy__(self, memo):
new = type(self)(
copy.deepcopy(self.path, memo), # ここは独立
self.cache # ここは共有
)
memo[id(self)] = new
return new
Python実務の使いどころ(テンプレート・スナップショット・安全化)
設定テンプレートを複製して加工(“元”を汚さない)
import copy
TEMPLATE = {
"path": ["in", "out"],
"opts": {"mode": "fast", "retry": 2},
}
cfg = copy.deepcopy(TEMPLATE)
cfg["path"].append("tmp")
cfg["opts"]["retry"] = 3
# TEMPLATE は不変
Pythonスナップショットを取って比較(差分検出)
処理前後のデータ構造を独立に保持して差分を計算。ログ・検証・ロールバックに有効。
import copy
before = get_state()
after = copy.deepcopy(before)
mutate(after)
diff = compute_diff(before, after)
Python関数内での“防御的”完全コピー(外部を守る)
外部から受け取った構造を破壊的に加工する必要があるなら、最初に deepcopy。呼び出し側のデータを汚さない。
import copy
def transform(doc: dict) -> dict:
w = copy.deepcopy(doc)
# ここから自由に変更
w.setdefault("meta", {})["processed"] = True
return w
Python例題で身につける(定番から一歩先まで)
例題1:シャロー vs ディープの違いを体感
import copy
a = {"items": [[1], [2]]}
shallow = a.copy()
deep = copy.deepcopy(a)
shallow["items"][0].append(9)
deep["items"][1].append(8)
print(a) # {'items': [[1, 9], [2]]} ← シャローで波及
print(deep) # {'items': [[1], [2, 8]]} ← ディープは独立
Python例題2:循環参照を含む構造のコピー
import copy
node = {}
node["self"] = node
clone = copy.deepcopy(node)
print(node is node["self"]) # True(元構造)
print(clone is clone["self"]) # True(複製側も循環維持)
print(clone is node) # False(別物)
Python例題3:部分的に共有を残す deepcopy 実装
import copy
class Cache: pass
class Model:
def __init__(self, data, cache):
self.data = data
self.cache = cache
def __deepcopy__(self, memo):
new = type(self)(copy.deepcopy(self.data, memo), self.cache)
memo[id(self)] = new
return new
m1 = Model({"nums": [1,2,3]}, Cache())
m2 = copy.deepcopy(m1)
m2.data["nums"].append(4)
print(m1.data["nums"]) # [1,2,3](独立)
print(m1.cache is m2.cache) # True(共有)
Python例題4:巨大構造は“必要階層だけ”コピーして軽量化
# 全体 deepcopy は重い → 編集対象ブロックだけ独立にする
doc = {"header": {"ts": 0}, "body": [{"id": 1}, {"id": 2}]}
body = [row.copy() for row in doc["body"]] # ここだけコピー
body[0]["id"] = 100
print(doc["body"][0]["id"]) # 1(影響なし)
Pythonまとめ
deepcopy は「外側も内側もすべて別物」にする完全コピーで、入れ子のミュータブル要素を安全に独立させます。= は参照共有、シャローコピーは外側のみ独立、deepcopy は末端まで独立。循環参照にも対応しますが、コストは重いため“必要最小限の階層コピー”との使い分けが鍵。コピー不適切な資源(ファイル・ソケット等)は再作成や共有設計を選び、必要なら deepcopy でポリシーを明文化する。テンプレート複製、スナップショット、関数内防御など、場面ごとに最短で“安全”を確保する癖をつけましょう。
