8
6

More than 1 year has passed since last update.

Python: mutableオブジェクトとimmutableオブジェクトを理解する

Last updated at Posted at 2021-11-25

はじめに

みなさんは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内では

  1. int型の10000というオブジェクトを生成し、 0x10088d9d0 に配置する
  2. xをオブジェクトに紐付ける

といった動作をする。

※細かいPython memory managementの動作については、また別の記事で。ここではオブジェクトとアドレスの概念が理解できればOK。

mutableオブジェクト

mutableオブジェクトに変更を加えた場合どうなるかをlistを例に見ていく。

>>> x = [1, 2]
>>> x
[1, 2]
>>> hex(id(x))
'0x1008c8f00'

Screenshot 2020-09-13 at 18.15.06.png

このlistオブジェクトを変更するために、appendしてみると下記のようになる。

>>> x.append(3)
>>> x
[1, 2, 3]
>>> hex(id(x))
'0x1008c8f00'

Screenshot 2020-09-13 at 18.06.59.png
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 の時点ではこのようになっていたのが

Screenshot 2020-09-13 at 21.36.47.png

元々生成されたいたintオブジェクト10000の値は変更されず、新しいオブジェクト10001が生成され、そちらを参照する。

Screenshot 2020-09-13 at 21.37.39.png

例外

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

Screenshot 2020-09-13 at 22.23.57.png

しかしながら、実際のところ、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] の参照先アドレスは 0x1008d11b00x1008b56c0 から変更されていないことが確認できる。

まとめ

元となるデータを保持しているオブジェクト自体は変更されず、新しいオブジェクトを生成し、そちらを参照するのがimmutableオブジェクト、データを保持しているオブジェクトそのものの値を変更できるのがmutableオブジェクトとなる。ただ、tupleのように複数のオブジェクトによって構成されているようなオブジェクトは、immutableであっても内部のオブジェクトがmutableの場合、そのオブジェクト自体の値の変更は可能。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6