Python のタプルとリストの違いをざっくり解説 - 変更不可能と再代入の意味を id と ctypes を使って理解しよう
Python のリストは変更できますが、タプルは要素を加えるといった変更はできません。
a = [1, 2, 3]
a.append(4)
b = (1, 2, 3)
b.append(4)
# AttributeError: 'tuple' object has no attribute 'append'
タプルには
- 要素を加える
- 要素を消す
- すべての要素を消す
といった機能がなく、どこか JavaScript の const に似ています。そこで
★ 要素を加えたり削除したりする可能性があるものはリスト
★ 要素を変更したくないものはタプル
と使い分けます。
タプルとリストの違いは辞書のキーになるかどうかでより明らかに
要素を変更できない性質をもつタプルは辞書のキーになります。
a = [1, 2, 3]
b = (1, 2, 3)
d = {b: 5}
print(d) # {(1, 2, 3): 5}
しかしリストは辞書のキーになりえません。
a = [1, 2, 3]
b = (1, 2, 3)
d = {a: 5}
# TypeError: unhashable type: 'list'
「タプルとリストの違いはなにか?」ときかれたら、私はこの現象をあげます。
おいおい、さっき JavaScript の const に似ていると言ったけど、タプルは再代入できるよ!
タプルは const と違い、定義されたあとに別のタプルを代入できます。
a = (1, 2, 3)
a = (4, 5, 6)
print(a) # (4, 5, 6)
PyCharm などのエディターはこうした記法を注意しますが、Python のエラーではありません。この性質から、私はさっき
タプルは変更できない
という表現でなく
タプルの要素は変更できない
と説明しました。しかし、とてもややこしいことに、タプルは変更できないという表現は正しい。この再代入は変更にあたらないからです。
「なにを言ってるんだ? (1, 2, 3)
から (4, 5, 6)
になったじゃないか」
と思うかもしれませんが、再代入は変更でなく再作成です。
タプルの再代入は再作成を意味する
実は、変数を再代入すると id が変わります。
a = (1, 2, 3)
print(id(a)) # 4304889472
a = (4, 5, 6)
print(a) # (4, 5, 6)
print(id(a)) # 4304965056
つまり、最初の a
と再代入したあとの a
は同じタプルでない。次のコードから Python がなにをやっているかわかります。
import ctypes
a = (1, 2, 3)
x = id(a)
print(f'x = {x}')
a = (4, 5, 6)
print(a)
print(f'id(a) = {id(a)}')
b = ctypes.cast(x, ctypes.py_object).value
print(b)
# x = 4373014144
# (4, 5, 6)
# id(a) = 4373089728
# (1, 2, 3)
再代入するまえに id を保存し、ctypes
をつかって (1, 2, 3)
を復元しています。タプルを再代入すると a
→ (1, 2, 3)
の assignment は消えますが、(1, 2, 3)
というオブジェクトそのものは消えません。
結局、タプルは変更できないのです。以上から、書籍などはしばしば
タプルの要素は変更できない
でなく
タプルは変更できない
という表現でタプルを説明します。一方、リストは要素を変更しても id
は同じ。
a = [1, 2, 3]
print(id(a)) # 4306118784
a.append(4)
print(id(a)) # 4306118784
だんだんタプルとリストの違いがわかってきたと思います。