Pythonの変数の参照について
変数の参照を調べると下記の記事を発見した。
https://qiita.com/ponnhide/items/cda0f3f7ac88262eb31e
結論は記事にある通りで自分の環境でも再現できたが、仕組みが理解できなかったので、調べてみた。
長々と書いたけど、つまり、
変数に新たなオブジェクトそのものが代入されるときには、これまでの参照先から変わって新たなオブジェクトが作られた場所を参照するようになる。
で実務的には問題ないはず。
ミュータブルとイミュータブル
Pythonのオブジェクトには、ミュータブルとイミュータブルの2種類あり、これを理解できれば変数変更時の挙動の仕組みを理解できる。
公式を見ると、ミュータブルとは、
Mutable objects can change their value but keep their id().
とある。ここで id()
はオブジェクトのIDを返す。したがって、id(A) == id(B)
でオブジェクトA, Bそれぞれの参照が同じかどうか判定できる。
このことを踏まえれば、 ミュータブルなオブジェクトを変更しても、id()
を維持する、つまり、参照を維持するとわかる。
簡単に試してみた。ミュータブルなオブジェクトを変更しても参照が維持されてることがわかる。
>>> a = [1, 2, 3]
>>> b = a
>>> print(a, b)
[1, 2, 3] [1, 2, 3]
>>> id(a) == id(b)
True
>>>
>>>
>>> b[0] = 4
>>> print(a, b)
[4, 2, 3] [4, 2, 3]
>>> id(a) == id(b)
True
>>>
次に、イミュータブルとは、
An object with a fixed value. Immutable objects include numbers, strings and tuples. Such an object cannot be altered. A new object has to be created if a different value has to be stored.
とある。上記の通り値を変更しても、新しいオブジェクトが作成されるので、参照が維持されない。
こちらも簡単に試してみた。イミュータブルなオブジェクトを変更すると参照が維持されないことがわかる。
>>> c = 1
>>> d = c
>>> print(c, d)
1 1
>>> id(c) == id(d)
True
>>>
>>>
>>> d = 3
>>> print(c, d)
1 3
>>> id(c) == id(d)
False
それでも分からなかったこと
下記の例を見てほしい。
>>> a = [1, 2, 3]
>>> b = a
>>> print(a, b)
[1, 2, 3] [1, 2, 3]
>>> id(a) == id(b)
True
>>>
>>>
>>> b = [4, 5, 6]
>>> print(a, b)
[1, 2, 3] [4, 5, 6]
>>> id(a) == id(b)
False
ミュータブルなオブジェクトであるリストに、別の値を代入すると参照が変わってしまった。
変更しても参照が変わらないのが、ミュータブルなオブジェクトじゃないのか?
上記の挙動の原因は公式で見つからなかった。
挙動からは、Pythonは変更と代入を区別すると考えられる。
さらに仮説として、代入時には新しいオブジェクトを作成し参照を変える、つまり、コピーオンライト的な挙動をしているのではないだろうか。
むすび
代入時の挙動の仕組みは見つからず仮説になってしまったが、変数変更時の挙動の仕組みは理解できた。
代入時の挙動をもう少し調べたい。
追記(2022/05/06)
@shiracamus様より補足していただきました。(補足のリンク)
ミュータブルなオブジェクトへの代入により参照が変わることについて、疑問が解消されました。
Pythonの値はすべてオブジェクトで、変数はオブジェクトへの参照を保持するだけです。
変数は変数辞書に格納され、変数名は変数辞書を参照するための辞書キー文字列でしかありません。
リストはさらに要素オブジェクトへの参照を保持します。
変数に代入してオブジェクトの参照先を変更するのか、変数を参照してオブジェクト内の参照先を変更するるのか、その違いを理解する必要があります。