環境設定 数値 文字列 正規表現 リスト タプル 集合 辞書 ループ 関数 クラス データクラス 時間 パス ファイル スクレイピング その他

Python のタプルとリストの違いをざっくり解説 - 変更不可能と再代入の意味を id と ctypes を使って理解しよう

最終更新日 2023.02.18

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

だんだんタプルとリストの違いがわかってきたと思います。