変数への累算代入とオブジェクトへの累算代入の違いを図で理解する

※ 他の方の記事のコメントに書いたのですが、みなさんにも知って欲しくて記事にしました。

## オブジェクト（値）と変数

Pythonでは、None、数値、関数、クラス、メソッド、モジュールなど **すべて** が **オブジェクト**（何らかのクラスのインスタンス）に **変換** されます。
**変数** は **オブジェクトへの参照値** である **オブジェクトid** を **保持** します。どんなオブジェクトでも **代入** できます。違う型のオブジェクトを **再代入** することもできます。
**変数** は、**変数名** を 辞書キー として、**オブジェクトid** を 辞書値 として **変数辞書** に格納されます。**変数辞書** の内容は、`vars`関数, `locals`関数, `globals`関数で確認できます。
**リスト** は **オブジェクトid** の **配列** です。配列要素毎に違う型のオブジェクトを 代入・再代入 することもできます。

## 変数代入・再代入

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29070/0d12c9d5-d0a5-fc27-8ea3-0335dc3b39dd.png)

```py
>>> id(1)
15812500000
>>> type(1)
<class'int'>
>>> 1 .__class__    # 小数点に解釈されないようにドットの前に空白を入れる
<class'int'>
>>> 1 .real
1
>>> id(2)
15812500032
>>> a = 1
>>> id(a)
15812500000
>>> a = 2
>>> id(a)
15812500032
>>> vars()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class'_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>,
 'a': 2}
```

## 変数への累算代入その１：イミュータブルオブジェクトの場合

`+=` のような 代入 を 累算代入 といいます。
Pythonの累算代入は、変数に代入されているオブジェクトが イミュータブル（不変）オブジェクト か ミュータブル（可変）オブジェクト かで動作が違います。
int型のようなイミュータブルオブジェクトの場合は、下図のように別オブジェクトを再代入します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29070/31bc8c4a-bf03-eb3f-3d00-19811d156d4a.png)

```py
>>> a = 2
>>> id(a)
15812500032
>>> a += 1
>>> id(a)
15812500064
```

## リストオブジェクト

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29070/bfd2af51-4d11-dd59-0640-cbdc18a8a9ac.png)

```py
>>> id(1)
15812500000
>>> id(2)
15812500032
>>> id(3)
15812500064
>>> a = [1, 2, 3]
>>> id(a)
123145300973832
>>> type(a)
<class'list'>
>>> a.__class__
<class'list'>
>>> len(a)
3
>>> a.__len__()
3
>>> for ele in a:
...     print(ele, type(ele), id(ele))
...
1 <class'int'> 15812500000
2 <class'int'> 15812500032
3 <class'int'> 15812500064
>>> vars()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class'_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>,
 'a': [1, 2, 3],
 'ele': 3}
```

## オブジェクトへの累算代入

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29070/782cb317-ef41-7af4-979c-0e3b07beb7cb.png)

```py
>>> a = [1, 2, 3]
>>> id(a[0])
15812500000
>>> a[0] += 1
>>> id(a[0])
15812500032
>>> id(a[1])
15812500032
```

なお、`-5` ～ `256` の整数値（int型オブジェクト）はよく利用される値としてオブジェクトがキャッシュされて **共有再利用** されるため、`a[0]` と `a[1]` の オブジェクトid が同じになっています。
オブジェクト指向の[デザインパターン](https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2))でいう「[flyweightパターン](https://ja.wikipedia.org/wiki/Flyweight_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)」が使われています。

## 変数への累算代入その２：ミュータブルオブジェクトの場合

リストはミュータブルオブジェクトのため、int型オブジェクトのときとは動作が異なります。
下図のようにオブジェクトに処理を依頼し、オブジェクト自身がオブジェクト内容を変更します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29070/402bc7e8-5392-5878-a106-f69d5f404986.png)

```py
>>> a = [1, 2, 3]
>>> id(a)
123145300973832
>>> hasattr(a, '__iadd__')
True
>>> hasattr(2, '__iadd__')
False
>>> a += [4]
>>> id(a)
123145300973832
>>> len(a)
4
>>> a.__len__()
4
>>> id(a[3])
15812500096
```

## 参考

累算代入文: https://docs.python.org/ja/3/reference/simple_stmts.html#augmented-assignment-statements
累算代入メソッド: https://docs.python.org/ja/3/reference/datamodel.html#object.__iadd__