Pythonに見られる誤解について説明します。
誤解1:イミュータブルに代入すると「イミュータブルだから新しいオブジェクトが作られる」
例
① X = 1
② X = 2
よくある誤解
①で X に 1 が代入される。
②で「1 を 2 に変更しようとする」が、1 はイミュータブルなので変更できない。
だから X には新しい 2 が作られて代入される。
正しい理解
Python の = は「代入」というより 束縛(名前付け) を行う。
-
X = 1は「Xの中身を1に書き換える」ではなく、
「名前 X が、整数オブジェクト 1 を参照するようにする」 -
X = 2は、
「名前 X が、整数オブジェクト 2 を参照するように結び直す(再束縛する)」
つまりこの2行は、オブジェクト1を2に変更しているのではなく、
Xの参照先を 1 から 2 に付け替えているだけ。
補足:イミュータブルが問題になるのは「オブジェクトそのものを変更しようとしたとき」であって、
=による束縛の付け替えとは別の話。
誤解が起きる原因の考察
X = 1 を「Xに1という値が入る」と教えられると、学習者は Xそのものが1になる(Xは1である) という認識になりやすい。
その結果、X = 2 を見たときに
- 「Xの中に入っている 1 を 2 に変える処理」
- 「1 を 2 に変更する(書き換える)処理」
だと誤解してしまう。
しかし実際には、= は「中身の書き換え」ではなく 名前の束縛(参照先の付け替え) であり、
X = 2 は Xが参照するオブジェクトを 1 から 2 に切り替える(再束縛する) だけである。
誤解2:関数引数がイミュータブルだと「値渡し」、ミュータブルだと「参照渡し」
よくある誤解
- イミュータブル(
int,str,tupleなど)を渡すと「値渡し」 - ミュータブル(
list,dict,setなど)を渡すと「参照渡し」
正しい理解
Pythonの関数呼び出しは一貫して 「オブジェクト参照(への束縛)を渡す」 方式。
- 呼び出し時に、引数名(仮引数)は渡されたオブジェクトへ束縛される
- そしてこの 引数名(仮引数)は、関数のローカル変数 である
→ 関数の外側の変数名とは別物(同名でも別スコープ)
よく使われる表現としては、次のどちらかが近いです:
- call by sharing(共有による呼び出し)
- call by object reference(オブジェクト参照渡し)
じゃあなぜ「イミュータブル=値渡し」に見えるの?
イミュータブルは 関数内で“中身を変更する”ことができない ので、結果として
- 関数内で代入(再束縛)しても
- 影響するのは ローカル変数である仮引数 だけ
- 呼び出し元の変数は変わらない
→ これが「値がコピーされた」ように見える原因。
例:イミュータブル(int)
def f(x):
x = x + 1 # x(ローカル変数)を別の int オブジェクトへ再束縛しているだけ
a = 10
f(a)
print(a) # 10 のまま
なぜ「ミュータブル=参照渡し」に見えるのか
ミュータブル(list / dict など)は 同じオブジェクトの中身を変更できる ため、関数内で破壊的操作をすると、呼び出し元も「同じオブジェクト」を見ているので状態の変化がそのまま観測されます。
def g(xs):
xs.append(99) # 同じ list オブジェクトの中身を変更
b = [1, 2]
g(b)
print(b) # [1, 2, 99]
誤解3:+= は常に「新しいオブジェクトを作る」
型によって挙動が違う
-
イミュータブル(
int,str,tupleなど)
→ だいたい 新規生成&再束縛 -
ミュータブル(
listなど)
→ 破壊的に変更 することがある
例1:イミュータブル(int)
a = 1
b = a
a += 1 # 新しい int が作られ、a が再束縛される
print(a) # 2
print(b) # 1(bは変わらない)
例2:イミュータブル(str)
s = "a"
t = s
s += "b" # 新しい str が作られ、s が再束縛される
print(s) # "ab"
print(t) # "a"(tは変わらない)
例3:ミュータブル(list)
a = [1, 2]
b = a
a += [3] # aの中身が変わる(結果としてbも変わる)
print(a) # [1, 2, 3]
print(b) # [1, 2, 3]
まとめ
Pythonでは「変数=箱」ではなく「名前→オブジェクトへの参照」と考える。
Pythonによくみられる誤解について説明しました。