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


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

オブジェクト(値)と変数

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

変数代入・再代入

>>> 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}

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

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

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

リストオブジェクト

>>> 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}

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

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

なお、-5256 の整数値(int型オブジェクト)はよく利用される値としてオブジェクトがキャッシュされて 共有再利用 されるため、a[0]a[1] の オブジェクトid が同じになっています。
オブジェクト指向のデザインパターンでいう「flyweightパターン」が使われています。

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

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

>>> 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__