Pythonタプルの不変性の罠
Pythonの可変性とは、Pythonオブジェクトの作成後に、そのオブジェクトを変更できるかどうかを指す。DictやListは可変、タプルやブールは不変というように。ざっくりまとめると一般的には以下のように習うことが多い。
タイプ | 概要 | 不変? | |
---|---|---|---|
bool | Boolean value | yes | |
int | 整数 | yes | |
float | 少数 | yes | |
list | 可変オブジェクトの集まり | no | |
tuple | 不変オブジェクトの集まり | yes | |
str | ストリング | yes | |
set | 集合のオブジェクトの集まり | no | |
frozenset | 不変の集合 | yes | |
dict | ディクショナリ | no | </figure> |
例えば不変性のオブジェクトを作成後に変更しようと以下のような事が起こる。
In [11]: my_tuple = (1,2,3)
In [12]: my_tuple[0]
Out[12]: 1
In [13]: my_tuple[0] = 10
TypeError Traceback (most recent call last) in
----> 1 my_tuple[0] = 10
TypeError: 'tuple' object does not support item assignment
しかし!もしタプルが可変性のオブジェクトを含む場合(リストとか)、注意が必要だ。
In [14]: my_tuple2 = ([1,2,3], [4,5,6])
In [15]: my_tuple2[0][2] = 10
In [16]: my_tuple2
Out[16]: ([1, 2, 10], [4, 5, 6])
この場合、Pythonは何も問題なくリストの変数を更新してしまう。これはタプルはリストの参照のみを含んでいるだけであって、タプルはリストの中身が何かまでは把握していないからだ。上記の例をより分かりやすく書き換えてみると、こうなる。
In [17]: list1 = [1,2,3]
In [18]: list2 = [4,5,6]
In [19]: my_tuple2 = (list1, list2)
In [20]: list1[2] = 10
In [21]: my_tuple2
Out[21]: ([1, 2, 10], [4, 5, 6])
つまり、タプルが不変だ!と言うときは、タプル自身のオブジェクトの参照のみを指していているだけなのだ。実際にタプルのリストの参照を更新しようとすると不変性のルールでエラーが出る。
In [22]: my_tuple2[0] = [2,3,4] TypeError Traceback (most recent call last) in
----> 1 my_tuple2[0] = [2,3,4]
TypeError: 'tuple' object does not support item assignment
不変性の本質についてちょっと詳しくなった!って思えたら、他のPython書く友達は同僚に教えてみてね!意外と知らない人いると思うので。
おまけ
他にもPythonのちょっとビックリするような動作がまとめられたレポジトリがあるので、確認してみてね!https://github.com/satwikkansal/wtfpython#-mutating-the-immutable (wft pythonは訳すると”Python、何だこれ!”的な意味)。
例えば、タプルに関してもう一つびっくりする例を出すなら。。。
In [23]: another_tuple = ([1, 2], [3, 4], [5, 6, 1000])
In [24]: another_tuple[2].append(1000) # エラーなし
In [25]: another_tuple[2] += [99, 999] TypeError Traceback (most recent call last) in
----> 1 another_tuple[2] += [99, 999]
TypeError: 'tuple' object does not support item assignment
In [26]: another_tuple
Out[26]: ([1, 2], [3, 4], [5, 6, 1000, 1000, 99, 999])