概要(「代入」と「コピー」をまず分けて考える)
コレクションのコピーは、元データを壊さず安全に加工するための基本です。まず重要なのは「= はコピーではない」ということ。= は“同じオブジェクトへの参照を共有”するだけで、片方を変更するともう片方も変わります。コピーには大きく「浅いコピー(shallow copy)」と「深いコピー(deep copy)」があり、用途によって使い分けます。
original = [1, 2, 3]
alias = original # 参照共有(コピーではない)
alias[0] = 99
print(original) # [99, 2, 3] ← 影響を受ける
Python代入・浅いコピー・深いコピー(ここが重要)
代入(参照共有)
代入は“同じものを別名で呼ぶ”だけ。高速ですが、独立させたい場面では使いません。誤用がバグの温床になります。
浅いコピー(1階層だけ複製)
浅いコピーは“外側のコンテナ”だけ新しく作り、中に入っている要素(参照)はそのまま共有します。要素が不変(int/str/tuple)なら十分ですが、要素がリストや辞書など“ミュータブル”だと中身の変更が共有されます。
import copy
a = [[1, 2], [3, 4]]
b = copy.copy(a) # a[:], list(a), a.copy() と同等(浅いコピー)
b[0][0] = 99
print(a) # [[99, 2], [3, 4]] ← 内側が共有されている
Python深いコピー(入れ子の中まで完全複製)
deepcopy は入れ子のミュータブル要素まで“辿って”新しく作り、元データと完全に独立させます。安全ですが、コスト(時間・メモリ)が高め。必要な場面だけ使いましょう。
import copy
a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)
b[0][0] = 99
print(a) # [[1, 2], [3, 4]] ← 影響なし(完全に独立)
Pythonコレクション別のコピー方法(一発で迷わない実用リスト)
リスト(list)
- 浅いコピー: a[:]、list(a)、a.copy()
- 深いコピー: copy.deepcopy(a)
a = [1, 2, 3]
shallow = a[:] # または a.copy()
deep = __import__("copy").deepcopy(a)
Python辞書(dict)
- 浅いコピー: a.copy()、dict(a)
- 深いコピー: copy.deepcopy(a)
- 注意: dict.keys()/values()/items() は“ビュー”でありコピーではありません(元の辞書変更が反映されます)。
a = {"name": "coffee", "info": {"price": 350}}
shallow = a.copy()
deep = __import__("copy").deepcopy(a)
Pythonセット(set)
- 浅いコピー: a.copy()、set(a)
- 深いコピー: copy.deepcopy(a)(内側にミュータブルが入っているタプルなどを含む場合のみ意味あり)
a = {1, 2, 3}
shallow = a.copy()
Pythonタプル(tuple)
- タプル自体は“不変”なのでコピーの必要は基本的にありません。同じ参照を共有して問題ありません。
- ただし“中にミュータブルが入っているタプル”は、deepcopy しないと内側が共有されます。
import copy
a = (1, [2, 3])
b = copy.deepcopy(a)
b[1][0] = 99
print(a) # (1, [2, 3])
Pythonよくある落とし穴と安全策(重要ポイントを深掘り)
「ビュー」と「コピー」を混同しない
dict.keys()/values()/items() はコピーではなく“現在の内容を映すビュー”。元辞書の更新が反映されます。値を固定したいときは list(…) などで“その時点のスナップショット”を作ります。
d = {"a": 1}
v = d.values() # ビュー
d["b"] = 2
print(list(v)) # [1, 2] ← 反映される
Pythonネスト構造は浅いコピーだと“中身が共有”される
内側のリスト・辞書に手を入れる予定があるなら、deepcopy 一択。浅いコピーで十分なのは「内側を変更しない前提」か「内側が不変オブジェクト」の場合です。
コストと必要性で選ぶ(深いコピーは最後の手段)
deepcopy は“全部辿る”ため重くなりがち。代替として:
- 変更が必要な部分だけを新規作成する(部分的 deepcopy)
- データ設計を“不変(イミュータブル)中心”にする(タプル、frozenset)でコピー自体を減らす
- 共有して良い部分は浅いコピー+注意書き(コメント)で運用
カスタムクラスのコピー
自作クラスは copy.copy()/copy.deepcopy() で動きますが、特殊なリソース(ファイル、ロック)を持つ場合は、copy/deepcopy を実装して“どう複製するか”を定義すると安全です。
import copy
class Box:
def __init__(self, items):
self.items = items
def __copy__(self):
return Box(self.items.copy())
def __deepcopy__(self, memo):
return Box(copy.deepcopy(self.items, memo))
Python例題で身につける(定番から一歩先まで)
例題1:設定テンプレートから安全に派生
import copy
TEMPLATE = {
"db": {"host": "localhost", "port": 5432},
"features": {"beta": False},
}
conf = copy.deepcopy(TEMPLATE)
conf["features"]["beta"] = True
print(TEMPLATE["features"]["beta"]) # False(元に影響なし)
Python例題2:浅いコピーで十分なケース(内側不変)
a = [{"id": 1, "tags": ("coffee", "drink")}, {"id": 2, "tags": ("tea",)}]
b = a[:] # 外側だけ新しく、内側のタプルは不変なので安全
b[0] = {"id": 1, "tags": ("bean",)} # 外側の差し替え
print(a[0]["tags"]) # ('coffee', 'drink')(内側に影響なし)
Python例題3:ビューとスナップショットの違い
d = {"coffee": 350}
vals_view = d.values() # ビュー
vals_list = list(d.values()) # スナップショット
d["tea"] = 280
print(list(vals_view)) # [350, 280](反映)
print(vals_list) # [350](固定)
Python例題4:部分的 deepcopy でコスト削減
import copy
a = {"meta": {"ver": 1}, "data": [{"x": 1}, {"x": 2}]}
b = a.copy() # 浅いコピー
b["data"] = copy.deepcopy(a["data"]) # 変更予定の部分だけ深く
b["data"][0]["x"] = 99
print(a["data"][0]["x"]) # 1(元は守られる)
Pythonまとめ
- = はコピーではなく参照共有。独立させたいなら必ずコピーを使う。
- 浅いコピーは“外側だけ新規”、入れ子のミュータブルは共有される。a[:], a.copy(), list(a)/dict(a)/set(a)。
- 深いコピーは入れ子の中まで複製。copy.deepcopy は安全だが重いので“必要な場面だけ”。
- dict の keys/values/items はビュー。固定したいときは list(…) でスナップショット。
- 設計で“不変を増やす”“部分的 deepcopy”を選ぶと、性能と安全性のバランスが取れる。
この判断軸を身につければ、初心者でも「壊さずに加工する」安全なコードが書けます。
