Pythonの代入の挙動について学習していたときに出くわした疑問です。
同じ整数を別々の変数に代入したら参照するオブジェクトのIDが同じに。なんで?
以下のように、変数 a、b に 10 をそれぞれ代入したとき、
>>> a = 10
>>> b = 10
自分の頭の中では、こんな↓風に「値はどちらも 10 だけどメモリ上には2つのオブジェクトが生まれ、それぞれにa, bの名前が付いている。ID(メモリアドレス)はもちろん別を指し示す」というイメージでした。Pythonの代入は束縛/bindというやつですね。
ところがどっこい、変数a, bが参照しているオブジェクトのIDを確認すると、
>>> id(a)
4343178640
>>> id(b)
4343178640
>>>
>>> a is b
True
>>>
# 同じ...!?!?!?
なんと全く同じIDを示していました。以下のように a も b も同じオブジェクトを参照している状態になっています。
b = a
としていたならこの結果で納得だったのですが、b に a を代入したわけではありません。
公式ドキュメントによると、代入前にすでに-5〜256までの整数オブジェクトがメモリに存在している
「python 変数 int id 同じ」などと検索してみても出てこなかったので、Google USで「python int same address」と検索したら解決しました。
Python WAT!? Integer Cache → Integer Objects(公式ドキュメント)
The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object.
現在の実装では、-5 から 256 までの全ての整数に対する整数オブジェクトの配列を保持するようにしており、この範囲の数を生成すると、実際には既存のオブジェクトに対する参照が返るようになっています。
ということなので、
「-5 〜 256 の整数はメモリ上で利用できるように準備OKの状態になっている。だからa = 10
b = 10
の代入が実行される時には 変数a も 変数b も既にメモリ上にある10
という整数オブジェクトを参照をするだけ。結果、IDは同じになる」 と理解しました。
メモリ上に展開されている/されていない境目になる 256, 257 の値で試してみると、
>>> a = 256
>>> b = 256
>>> id(a)
4343186512
>>> id(b)
4343186512 # IDが一緒
>>> a is b
True
>>>
>>> a = 257
>>> b = 257
>>> id(a)
4346386992
>>> id(b)
4346387088 # IDが異なる!!
>>> a is b
False
>>>
公式ドキュメントの想定通りに、257から 変数a と 変数b で参照するオブジェクトが異なることが確認できました。
以下サイトを読むと、「-5 〜 256の数値は頻繁に使われ、パフォーマンスのためにすぐに利用できるようにしておく」のが目的とのこと。
Real Python: Small Integer Caching
整数ではなく、浮動小数点数で実験
「整数オブジェクトの配列を保持するようにしており〜」とあったので、浮動小数点数で試してみたら...
>>> a = 0.5
>>> b = 0.5
>>> id(a)
4317942456
>>> id(b)
4317941880 # IDが異なる
>>> a is b
False
>>>
値自体は同じでも、浮動小数点数の場合はIDが異なる結果に。これもドキュメント通り整数のみ、ということが確認できました。
試した環境
Python 3.7.1