[Python] タプルには代入できないがタプル内リストには代入できるという話

環境:Windows10 + Python 3.7.6

リストとタプルの違い

Pythonの組み込み型には、配列を表現できるリストタプルという二つの型が用意されている。この二つには、値の変更を許すかどうかに違いがある。

リスト:要素の変更を許す
タプル:要素の変更を許さない

リストの要素を変更することはできるが、タプルの要素を変更することはできない。もし無理に変更しようとすれば、エラーとなる。

コード 1

x = [1, 2, 3]
print(x)
x[0] = 9
print(x)

出力 1

[1, 2, 3]
[9, 2, 3]


コード 2

x = (1, 2, 3)
print(x)
x[0] = 9
print(x)

出力 2

(1, 2, 3)
Traceback (most recent call last):
  File "XXX.py", line 3, in <module>
    x[0] = 9
TypeError: 'tuple' object does not support item assignment

何度も変更する予定の場合はリストを使い、変更する予定がない場合はバグを防ぐためにタプルを使う。というのが基本的な方針である。

では、タプルの中身は変更されないと考えてよいだろうか?

実はそうとは限らない。以下に紹介するタプル内リストの要素を変更する場合、エラーが出ることなくタプルの中身を変更することができてしまう。

タプル内リストの落とし穴

次のコードではタプルの第一要素であるリストを変更しようとしているが、もちろんこれはエラーが出る。

コード 3

x = ([1, 1], [2, 2], [3, 3])
print(x)
x[0] = [9, 9]
print(x)

出力 3

([1, 1], [2, 2], [3, 3])
Traceback (most recent call last):
  File "XXX.py", line 3, in <module>
    x[0] = [9, 9]
TypeError: 'tuple' object does not support item assignment

しかし、次のコードが示すように、タプル内リストの要素を変更することはできる。なぜなら、タプル側が参照しているリスト自体は変わっていないためである。タプルからすると、同じリストを参照していることは保証しているが、リストの中身が同じであることまでは保証してくれないのである。

コード 4

x = ([1, 1], [2, 2], [3, 3])
print(x)
x[0][0] = 9
print(x)

出力 4

([1, 1], [2, 2], [3, 3])
([9, 1], [2, 2], [3, 3])

解決策:タプル内リストを避ける

上で紹介したような奇妙な挙動は、バグを引き起こす原因となりやすい。そのため、プログラムの中ではタプル内リストを避けるのが良い。状況に応じてタプル内タプルかリスト内リストを使えば良いだろう。

変更予定のある変数 --> リスト内リストを使う
変更予定のない変数 --> タプル内タプルを使う