ミュータブルとイミュータブル
Pythonにおけるデータ保持の考え方
Pythonでは、変数に以下のようにデータを格納した際、変数が直接データ(オブジェクト)を持つのではなく、オブジェクトがどこに格納されているか(メモリアドレス)を保持する。
i = 1
print(id(i))
#11111111のようなメモリアドレスが表示される。
リストについても同様にリストに格納するデータも、オブジェクトではなく、メモリアドレスを保持している。
i = [1,2,3]
print(id(i[0]))
#22222222のようなメモリアドレスが表示される。
ミュータブルとイミュータブル
イミュータブル
イミュータブルとは、同じメモリアドレスのまま、値を変更できないオブジェクトである。
次のように、同じ変数に値を代入しようとしても、メモリアドレスが変わってしまう。
これは数値や文字列がイミュータブルで、代入するたびに新たにオブジェクトが生成されるためである。
i = 1
print(id(i))
#11111111のようなメモリアドレスが表示される。
i = 2
print(id(i))
#22222222のようなメモリアドレスが表示される。
ミュータブル
ミュータブルとは、同じメモリアドレスのまま、値を変更できるオブジェクトである。
リストはミュータブルであるため、以下のようにリストの値を変更してもリストのメモリアドレスは変わらない。これはリストがミュータブルのため、リスト自体は格納されるデータが変わっても新規にオブジェクトを作成するといったことはしない。
i = [1,2,3]
print(id(i))
#22222222のようなメモリアドレスが表示される。
i[0] = 3
print(id(i))
#22222222のようなメモリアドレスが表示される。
タプルはイミュータブルのため、上記のような代入をするとエラーが発生する。
i = (1,2)
i[0] = 3
#Errorが発生する
学んだこと
リストとタプルについて比較した際に、タプルがイミュータブルな理由がよくわからなくなった。
タプルも要素を変更した際に、別にタプル自体のアドレスは変わらないのではないか、と。これは私の根本的な考え方が違った。上記では代入してもそのオブジェクト自体のアドレスが変わらないことイコールミュータブルと説明していたが、イミュータブルかミュータブルかは別にそのオブジェクト自体のアドレスに関係がない。
そうではなく、メモリレイヤで、リストなどは要素のアドレスが変わることを許容し、タプルはそれを許容しない(固定する)、ということだと理解した。
また、この点において、数値や文字列のイミュータブルとタプルのそれとでは意味合いが違うように感じた。GPTくんに聞くと以下。
数値 (int, float) や文字列 (str) の場合、オブジェクトの値そのものが変更できない ことを意味します。
タプルの場合、タプル自身(オブジェクト全体)の内部構造が変更できない ことを意味します。
参考
記事を書くにあたった元教材
Discussion