はじめに
みなさんはmutable(ミュータブル)オブジェクト、immutable(イミュータブル)オブジェクトの違いについて、理解しているだろうか?大抵の場合「変更できるのがmutable、変更できないのがimmutable」のような答えが返ってくる。もちろん間違いではないのだが、変更できないというのは具体的にどういうことかと聞くと答えに窮する方が多かったので、本記事で整理しておく。
準備知識
id()
Pythonのbuilt-in関数である**id()は、メモリ内のオブジェクトのアドレス(位置)のIDを整数で返す。本記事では、メモリアドレスを16進数で表現するためにhex()を併用する。なお、Pythonの比較演算子であるis**がTrueになるのは、2つの変数が同じidを返す時である。( = 2つの変数が同一のオブジェクトを参照している)
Pythonオブジェクトリファレンス
例えばこのような変数を宣言する
>>> x = 10000
>>> hex(id(x))
'0x10088d9d0'
簡単に説明するとPython内では
- int型の10000というオブジェクトを生成し、 0x10088d9d0 に配置する
- xをオブジェクトに紐付ける
といった動作をする。
※細かいPython memory managementの動作については、また別の記事で。ここではオブジェクトとアドレスの概念が理解できればOK。
mutableオブジェクト
mutableオブジェクトに変更を加えた場合どうなるかをlistを例に見ていく。
>>> x = [1, 2]
>>> x
[1, 2]
>>> hex(id(x))
'0x1008c8f00'
このlistオブジェクトを変更するために、appendしてみると下記のようになる。
>>> x.append(3)
>>> x
[1, 2, 3]
>>> hex(id(x))
'0x1008c8f00'
0x1008c8f00 上のlistオブジェクトの値 [1, 2] が [1, 2, 3] になっただけで、xの参照オブジェクトのアドレスに変化はない。
immutableオブジェクト
同じように、ここではintオブジェクトに変更を加えてみる。
>>> x = 10000
>>> x
10000
>>> hex(id(x))
'0x10088da10'
>>> x = x + 1
>>> x
10001
>>> hex(id(x))
'0x10088dad0' <---- idの値が変わっている
immutableオブジェクトを変更しようとすると、このように新しいオブジェクトが生成され、 x は新しいオブジェクトを参照する。その結果、idの値が元の 0x10088da10 から 0x10088dad0 に変わっている。これがimmutableオブジェクトにおける動作で、冒頭の immutableオブジェクトは変更できない は、既にメモリ上作成されていた元オブジェクトに対して変更することができないということになる。そして、もとよりlistにおけるappendやpopのようなオブジェクトの値を操作するための関数が用意されていない。
x = 10000 の時点ではこのようになっていたのが
元々生成されたいたintオブジェクト10000の値は変更されず、新しいオブジェクト10001が生成され、そちらを参照する。
例外
immutableオブジェクトに関連する全てがimmutableなわけではない。例えばtupleで下記のようなオブジェクトがあったとする。
>>> x = ('hello', [1, 2])
>>> hex(id(x))
'0x1008c8580'
>>> hex(id(x[0])), hex(id(x[1]))
('0x1008d11b0', '0x1008b56c0')
tupleオブジェクトはimmutableなので、 x[1] のオブジェクトを他のオブジェクトに変更しようとするとexceptionが発生する。
>>> x[1] = 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
しかしながら、実際のところ、tupleの値はオブジェクトへのバインディング(結びつき、紐付き)であり、tuple内のそれぞれのindexがどのアドレスのオブジェクトへ紐付いているという情報を変更することはできないが、バインドされているオブジェクトそのものの値の変更は可能である。
>>> x[1].append(3)
>>> x
('hello', [1, 2, 3])
>>> hex(id(x[0])), hex(id(x[1]))
('0x1008d11b0', '0x1008b56c0') <- それぞれのindexの参照先のアドレスは変わっていない
このようにtuple内のmutableオブジェクト x[1] の値を変更することは可能であるが、tupleオブジェクト内の x[0] と x[1] の参照先アドレスは 0x1008d11b0 と 0x1008b56c0 から変更されていないことが確認できる。
まとめ
元となるデータを保持しているオブジェクト自体は変更されず、新しいオブジェクトを生成し、そちらを参照するのがimmutableオブジェクト、データを保持しているオブジェクトそのものの値を変更できるのがmutableオブジェクトとなる。ただ、tupleのように複数のオブジェクトによって構成されているようなオブジェクトは、immutableであっても内部のオブジェクトがmutableの場合、そのオブジェクト自体の値の変更は可能。