【Python】変数に代入したら元の変数まで変わる!?原因と対策を分かりやすく解説
Pythonでリストをコピーしたつもりで別の変数に代入し、その変数を書き換えたら、なぜか元の変数まで変わっていた。Pythonを学び始めた人の多くが一度はぶつかる現象です。
a = [1, 2, 3]
b = a # コピーしたつもり
b.append(4) # b だけ変えたつもり
print(a) # [1, 2, 3, 4] ← a まで変わっている!
print(b) # [1, 2, 3, 4]
先に結論を書きます。
- 原因:
b = aはコピーではなく、同じリストに2つ目の名前を付けただけだから - 対策:別物にしたいときは
a.copy()でコピーする。リストの中にリストがあるときはcopy.deepcopy(a)を使う
この記事では、なぜこうなるのかを図のイメージで説明し、よくあるハマりどころと対策を実際のコードで紹介します。
なぜ元の変数まで変わるのか?
変数は「箱」ではなく「名札」
プログラミングの入門書では、変数を「値を入れる箱」にたとえることがよくあります。この考え方だと、b = a は「aの箱の中身を、bの箱にコピーする」ように思えます。
しかしPythonの変数は、箱というより値(オブジェクト)に付ける名札です。
a = [1, 2, 3] # [1, 2, 3] というリストを作り、「a」という名札を付ける
b = a # 同じリストに「b」という名札も付ける
このとき、リストは1つしかありません。1つのリストに「a」と「b」の2枚の名札が付いている状態です。だから b を通してリストを書き換えると、同じリストを指している a から見ても変わっているのです。
| 思っていたイメージ | 実際に起きていること |
|---|---|
| aの箱:[1, 2, 3] bの箱:[1, 2, 3](コピー) |
リスト [1, 2, 3] が1つだけあり、 そこに名札「a」と名札「b」が付いている |

同じものかどうかは id() と is で確かめられる
2つの変数が同じオブジェクトを指しているかは、id() 関数や is 演算子で確認できます。
a = [1, 2, 3]
b = a
print(id(a), id(b)) # 同じ番号が2つ表示される(番号は実行ごとに変わる)
print(a is b) # True(同じオブジェクト)
id() はオブジェクトごとに付いている識別番号を返します。番号が同じなら同じオブジェクトです。詳しくは「Pythonのid()とは?使い方・戻り値・オブジェクトの識別について解説」で解説しています。
数値や文字列では起きないのはなぜ?
「でも、数値なら代入しても元の変数は変わらなかったよ?」と思った人もいるでしょう。
x = 10
y = x
y += 5
print(x) # 10 ← 変わらない
print(y) # 15
これは、数値や文字列がイミュータブル(変更できない)なオブジェクトだからです。y += 5 は「10を15に書き換える」のではなく、15という新しい数値を作って、名札「y」をそちらに付け替える動きをします。10は書き換えられないので、名札「x」が付いている10はそのままです。
一方、リストや辞書はミュータブル(変更できる)なオブジェクトです。append() などはオブジェクトそのものを書き換えるので、同じオブジェクトを指すすべての名札に影響します。
| 種類 | 主な型 | 代入した先で書き換えると |
|---|---|---|
| ミュータブル(変更できる) | list、dict、set、自作クラスのインスタンス | 元の変数も変わる |
| イミュータブル(変更できない) | int、float、str、tuple、bool | 元の変数は変わらない(新しいオブジェクトができる) |
ミュータブルとイミュータブルの違いは「【Python入門】ミュータブルとイミュータブルの違いをわかりやすく解説」で詳しく解説しています。
「代入し直す」と「中身を書き換える」は別物
リストでも、新しいリストを代入し直した場合は元の変数に影響しません。
a = [1, 2, 3]
b = a
b = [9, 9] # 新しいリストに名札「b」を付け替えただけ
print(a) # [1, 2, 3] ← 変わらない
print(b) # [9, 9]
元の変数まで変わるのは、append()、b[0] = 100、b["key"] = 値 のように、オブジェクトの中身を書き換えたときだけです。
対策1:copy() でコピーを作る
別物として扱いたいときは、代入ではなくコピーを作ります。
a = [1, 2, 3]
b = a.copy() # 新しいリストを作る
b.append(4)
print(a) # [1, 2, 3] ← 変わらない
print(b) # [1, 2, 3, 4]
print(a is b) # False(別のオブジェクト)
リストのコピーには、次のような書き方もあります。どれも結果は同じです。
b = a.copy() # copy()メソッド
b = a[:] # スライス(全体を切り出す)
b = list(a) # list()で作り直す
辞書も copy() でコピーできます。
p = {"name": "田中", "age": 20}
q = p.copy()
q["age"] = 21
print(p) # {'name': '田中', 'age': 20} ← 変わらない
print(q) # {'name': '田中', 'age': 21}
copy() の使い方は「Python copy()とは?リストや辞書をコピーする方法を初心者向けに解説」で解説しています。
対策2:リストの中にリストがあるなら deepcopy()
ここで、もう一段階の落とし穴があります。リストの中にリストが入っている場合、copy() でも元の変数が変わることがあります。
a = [[1, 2], [3, 4]]
b = a.copy()
b[0].append(99)
print(a) # [[1, 2, 99], [3, 4]] ← また a まで変わった!
print(b) # [[1, 2, 99], [3, 4]]
copy() がコピーするのは外側のリストだけで、中に入っているリストは同じものを指したままだからです。これを浅いコピー(シャローコピー)と呼びます。
print(a is b) # False(外側のリストは別物)
print(a[0] is b[0]) # True(中のリストは同じもの)
中身まで丸ごとコピーしたいときは、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]]
| 方法 | コピーされる範囲 | 使いどころ |
|---|---|---|
b = a |
コピーしない(同じものに名札を付けるだけ) | 同じものを別の名前で扱いたいとき |
a.copy() など |
外側だけ(浅いコピー) | 中身が数値や文字列だけのリスト・辞書 |
copy.deepcopy(a) |
中身まですべて(深いコピー) | リストの中のリスト、辞書の中のリストなど |

浅いコピーと深いコピーの違いは「Python copy()とdeepcopy()の違い|浅いコピー・深いコピーを解説」、deepcopy() の詳しい使い方は「Python deepcopy()とは?ネストしたリストを完全にコピーする方法」で解説しています。
よくあるハマりどころ4選
「代入したら元の変数まで変わる」現象は、b = a 以外の形でもよく起きます。どれも原因は同じで、1つのオブジェクトを複数の場所から指していることです。
1. [[0] * 3] * 3 で2次元リストを作る
grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]] ← 全部の行が変わった!
![[[0] * 3] * 3 では3行が同じリストを指し、内包表記では行ごとに別のリストになることを示した図](/wp-content/uploads/2026/09/python-2d-list-otoshiana.png)
* 3 は同じリストを3回並べるだけなので、3行とも同じリストを指しています。2次元リストは内包表記で作りましょう。
grid = [[0] * 3 for _ in range(3)]
grid[0][0] = 1
print(grid) # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
2. 関数に渡したリストが書き換わる
def add_item(items):
items.append("new")
return items
fruits = ["apple"]
result = add_item(fruits)
print(fruits) # ['apple', 'new'] ← 呼び出し元のリストまで変わった!
関数の引数も「名札」なので、関数の中で書き換えると呼び出し元のリストも変わります。元のリストを残したいなら、関数の中でコピーしてから書き換えます。
def add_item(items):
items = items.copy() # コピーしてから書き換える
items.append("new")
return items
fruits = ["apple"]
result = add_item(fruits)
print(fruits) # ['apple'] ← 変わらない
print(result) # ['apple', 'new']
関数への渡し方の仕組みは「Pythonの参照渡しとは?値渡しとの違いと注意点を初心者向けに解説」で解説しています。
3. デフォルト引数に空のリストを使う
def add_log(msg, logs=[]):
logs.append(msg)
return logs
print(add_log("a")) # ['a']
print(add_log("b")) # ['a', 'b'] ← 前回の分が残っている!
デフォルト引数のリストは、関数を定義したときに1回だけ作られ、呼び出しのたびに同じリストが使い回されます。None を使って、呼び出しのたびに新しいリストを作るのが定番の書き方です。
def add_log(msg, logs=None):
if logs is None:
logs = []
logs.append(msg)
return logs
print(add_log("a")) # ['a']
print(add_log("b")) # ['b']
デフォルト引数の注意点は「Pythonデフォルト引数完全ガイド」でも解説しています。
4. ループの外で作ったリストを何度も追加する
rows = []
row = []
for i in range(3):
row.append(i)
rows.append(row)
print(rows) # [[0, 1, 2], [0, 1, 2], [0, 1, 2]] ← 全部同じになった!
rows には同じ row が3回追加されているだけなので、最後の状態が3つ並んで見えます。ループのたびに新しいリストを作りましょう。
rows = []
for i in range(3):
row = [i] # ループのたびに新しいリストを作る
rows.append(row)
print(rows) # [[0], [1], [2]]
pandasやNumPyでも同じことが起きる
データ分析でよく使う pandas や NumPy でも、似た現象が起きます。DataFrameやNumPy配列の一部を取り出すと、元のデータとメモリを共有したビューになることがあり、取り出した側を書き換えると元のデータまで変わる場合があります。
この場合も、.copy() で明示的にコピーを作るのが基本の対策です。詳しくは「Pandas DataFrameの「ビュー」と「コピー」を徹底解説」や「NumPy配列:ビュー?コピー?メモリ共有を正確に判定する方法」をご覧ください。
元の変数が変わってしまったときのチェックリスト
「なぜか元のデータが変わる」と思ったら、次の順に確認してみてください。
- 書き換えているのはリスト・辞書などのミュータブルなオブジェクトか?(数値や文字列なら、この現象は起きない)
b = aのように、コピーせずに代入していないか? →a.copy()にする- 中にリストや辞書が入っていないか? →
copy.deepcopy(a)にする - 関数の中で引数のリストを書き換えていないか? → 関数の中でコピーする
- デフォルト引数に
[]や{}を使っていないか? →Noneにする - 困ったら
a is bやid()で、同じオブジェクトかどうかを確かめる
よくある質問(FAQ)
Q. なぜPythonは代入でコピーしないのですか?
大きなデータを代入のたびにコピーすると、時間もメモリも無駄になるからです。代入は名札を付けるだけにしておき、コピーが必要なときだけ明示的にコピーする、という設計になっています。
Q. copy() と deepcopy() はどちらを使えばいいですか?
リストや辞書の中身が数値や文字列だけなら copy() で十分です。中にリストや辞書が入っているなら deepcopy() を使います。迷ったら deepcopy() を使えば安全ですが、データが大きいと処理が遅くなる点に注意してください。
Q. タプルなら元の変数は変わりませんか?
タプル自体は書き換えられませんが、中にリストが入っている場合、そのリストは書き換えられます。たとえば t = ([1, 2], 3) の t[0].append(9) は実行でき、t は ([1, 2, 9], 3) になります。
Q. 文字列を += で書き換えても元の変数は変わりませんか?
変わりません。文字列はイミュータブルなので、+= は新しい文字列を作って名札を付け替えるだけです。
Pythonの基礎をしっかり身につけたい方へ
「代入」「コピー」「ミュータブル」のような仕組みは、エラーが出ないぶん独学では気づきにくいポイントです。変数の基本から確認したい方は「【Python入門】変数の使い方を初心者向けに完全解説!」、学習の進め方は「Python 始め方|初心者向け学習ロードマップと環境構築の手順」をご覧ください。
テックジムでは、未経験から参加できる無料のPython講座を毎週開いています。つまずきやすいポイントを、実際にコードを動かしながら確認できます。詳しくは「プログラミング無料体験・無料講座|テックジムの無料Python講座・Claude Code講座」をご覧ください。






