概要(シャローコピーは「外側だけ新しく、中身は同じものを指す」コピー)
リストのシャローコピー(浅いコピー)は、リストそのもの(外側の入れ物)だけを新しく作り、要素がオブジェクトならその参照をそのままコピーします。数値や文字列のようなイミュータブルは実質影響しづらい一方、入れ子のリストなどミュータブル要素は「元とコピーで中身が共有」されます。これが“思ったより連動してしまう”原因です。シャローコピーの作り方は複数あり、用途に応じて最短の書き方を選びます。
# 代表的なシャローコピーの作り方
a = [1, 2, 3]
b = a[:] # スライス
c = list(a) # コンストラクタ
d = a.copy() # メソッド
import copy
e = copy.copy(a) # モジュール
Python基本の使い方(ここが重要)
「= はコピーではなく参照の代入」から押さえる
代入演算子「=」は、同じリストを別名で指すだけです。片方の変更がもう片方にも反映されます。まずここを誤らないことが最重要です。
x = [1, 2, 3]
y = x # コピーのつもりでも、同じものを指す
y[0] = 100
print(x) # [100, 2, 3] 連動してしまう
Pythonシャローコピーは「入れ物は別物、中身の参照は同じ」
外側だけが新しくなり、要素への参照は共有されます。イミュータブルは代入で“置き換え”になるので共有の問題が表面化しにくく、ミュータブル要素は中身の変更が双方に波及します。
a = [[1, 2], [3, 4]]
b = a[:] # シャローコピー
b[0].append(99) # 内側リストを“変更”
print(a) # [[1, 2, 99], [3, 4]] 共有されているため変わる
print(b is a, b[0] is a[0]) # False, True(外側は別、中は同じ)
Python重要ポイントの深掘り(方法の違い・入れ子構造・防御的コピー)
代表的な作り方の違いと選び方
スライス a[:] は最短で、list(a) は可読性が高く、a.copy() はリストであることが明示的です。copy.copy(a) は「シャローを明言」でき、型がリスト以外に変わっても同じ意図で使えます。どれも「外側1階層だけ新しくする」点は同じです。
a = [1, 2, 3]
b = a[:]
c = list(a)
d = a.copy()
import copy
e = copy.copy(a)
Python入れ子で“罠”になる理由と確認方法
内側のオブジェクトが共有されるため、変更が伝播します。id() で参照が同じか確認すると、仕組みを掴めます。
a = [[1], [2]]
b = a.copy()
print(id(a), id(b)) # 外側は別
print(id(a[0]), id(b[0])) # 内側は同じ → 共有
Python防御的コピー(必要な階層だけ手動でコピー)
深いコピーが重い・不要なときは、「変わりうる階層だけ」自分でコピーします。内側が一次元のときは内側もシャローコピーして独立させるのが現実的です。
a = [[1, 2], [3, 4]]
# 内側も“1階層だけ”コピーして防御
b = [inner[:] for inner in a]
b[0].append(99)
print(a) # 影響しない
Python完全に独立させたいならディープコピー
入れ子が多段のときや、共有による副作用を完全に排除したいときは deepcopy を使います。ただしコストは上がります。
import copy
a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)
b[0].append(99)
print(a) # 変わらない
Python実務の勘所(使い分け・性能・関数引数)
どれを使うべきかの指針
入れ子がない(または中身を変更しない)ならシャローコピーで十分です。入れ子があり、内側を変える可能性があるなら「防御的コピー」か deepcopy。コレクションが大きい場合、深いコピーは時間とメモリを食うため、必要最小限の階層だけコピーする方が現実的です。
性能の見方(シャローは軽い、ディープは重い)
シャローコピーは外側だけ作るため高速で軽量です。ディープコピーは再帰的に辿るため、サイズや階層に比例してコストが増えます。テストで計測し、必要な範囲に留めましょう。
関数引数での「思わぬ連動」を避ける
引数で受け取ったリストを関数内で破壊的に変更するなら、意図的にシャローコピーを取ってから操作すると安全です。呼び出し側のデータを守れます。
def safe_process(xs):
ys = xs[:] # 防御的コピー
ys.append("done")
return ys
original = [1, 2]
result = safe_process(original)
print(original) # 変わらない
Python例題で身につける(定番から一歩先まで)
例題1:スライス・list・copy の等価性を確認
a = [1, 2, 3]
b, c, d = a[:], list(a), a.copy()
b[0] = 99
print(a, b, c, d) # a は変わらない、b/c/d は別物
Python例題2:入れ子リストでの共有の可視化
a = [[1, 2], [3, 4]]
b = a.copy()
b[1].remove(4)
print("a:", a) # [[1, 2], [3]] 共有のため影響
print("b:", b) # [[1, 2], [3]]
Python例題3:一次入れ子だけ守る防御的コピー
a = [[1, 2], [3, 4]]
b = [row.copy() for row in a] # row[:] でも同じ
b[0].append(99)
print(a) # [[1,2],[3,4]]
print(b) # [[1,2,99],[3,4]]
Python例題4:設定テンプレートを安全に加工(必要階層のみコピー)
template = {"path": ["in", "out"], "opts": {"mode": "fast", "retry": 2}}
# ここでは“pathリストだけ”変更したい → その階層だけコピー
cfg = dict(template) # 外側はシャロー
cfg["path"] = list(template["path"]) # 該当階層を独立
cfg["path"].append("tmp")
print(template["path"]) # 影響しない
Pythonまとめ
シャローコピーは「外側だけ新しくして、中身の参照は共有する」コピーです。= はコピーではなく参照の代入、シャローは a[:], list(a), a.copy(), copy.copy(a) で作れる。入れ子のミュータブル要素は共有されるため、変更が伝播します。必要な階層だけ手動でコピーする防御的コピー、完全独立なら deepcopy を使い分ける。性能と安全性のバランスを取り、関数引数やテンプレート加工では“意図的なコピー”を癖にすると、予期せぬ連動バグを避けて、読みやすく堅牢なコードになります。
