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

## はじめに

みなさんはmutable（ミュータブル）オブジェクト、immutable（イミュータブル）オブジェクトの違いについて、理解しているだろうか？大抵の場合「変更できるのがmutable、変更できないのがimmutable」のような答えが返ってくる。もちろん間違いではないのだが、変更できないというのは具体的にどういうことかと聞くと答えに窮する方が多かったので、本記事で整理しておく。

## 準備知識

### id()

Pythonのbuilt-in関数である**[id()](https://docs.python.org/3.8/library/functions.html#id)**は、メモリ内のオブジェクトのアドレス（位置）のIDを整数で返す。本記事では、メモリアドレスを16進数で表現するために**[hex()](https://docs.python.org/3.8/library/functions.html#hex)**を併用する。なお、Pythonの比較演算子である**is**がTrueになるのは、2つの変数が同じidを返す時である。（ = 2つの変数が同一のオブジェクトを参照している）

### Pythonオブジェクトリファレンス

例えばこのような変数を宣言する

```
>>> x = 10000
>>> hex(id(x))
'0x10088d9d0'
```

簡単に説明するとPython内では

1. int型の10000というオブジェクトを生成し、 **0x10088d9d0** に配置する
3. 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657737/6e2f15f6-22ac-adf8-5e8c-6020f550f400.png)


このlistオブジェクトを変更するために、[append](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists)してみると下記のようになる。

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

![Screenshot 2020-09-13 at 18.06.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657737/47487c1e-7168-07c4-5400-f8c81aa3a691.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657737/12b4902d-058c-7c12-d80f-7ca03df92a4a.png)

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

![Screenshot 2020-09-13 at 21.37.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657737/4679505a-7d56-2b96-4c72-4e2c0356bdf4.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657737/79b413d2-bed0-1ee3-af93-0f6de3068029.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]** の参照先アドレスは **0x1008d11b0** と **0x1008b56c0** から変更されていないことが確認できる。


## まとめ

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