【Python】tupleとlistの違い

python

pythonにはtuple(タプル)とlist(リスト)という似たデータ構造があります。
プログラミングを勉強していると、大体のことはリストで事足りるため、そもそもタプルを使ったことがないという方もいるかもしれません。

この記事では、タプルとリストの共通点と相違点について、サンプルコードと一緒に解説していきます。

 

共通点

要素の列である

タプルができることは、大体リストで代用がききます。
というのも、タプルもリストも要素の列を取り扱うデータ構造であるからです。

例えば0,1,2という三つの整数を順番に持つタプルとリストは、pythonで以下のように宣言します。

>>> a = [0,1,2] # list
>>> a
[0, 1, 2]
>>> b = (0,1,2) # tuple
>>> b
(0, 1, 2)

aがリスト、bがタプルです。
リストは角括弧、タプルは丸括弧で宣言することに注意してください。
括弧以外は、ほとんど見た目に差がありません。

 

インデックスで要素にアクセスできる

タプルはリストと同様に、インデックスを指定して要素を抜き出すことができます。

>>> a = [0,1,2]
>>> a[1] # listの2番目
1
>>> b = (0,1,2)
>>> b[1] # tupleの2番目
1

このように、抜き出す部分だけを見ればリストもタプルも全く見た目が変わりません。

 

スライスが使える

さらに、スライスによる部分の抜き出しも、タプルとリストの両方で使うことができます。

>>> a = [0,1,2]
>>> a[0:2] # 2番目までを抜き出す
[0, 1]
>>> b = (0,1,2)
>>> b[0:2] # 2番目までを抜き出す
(0, 1)

ここで[0:2]の0は省略できますが、わかりやすさのためにあえて書いています。
スライスを用いることで、リストであれば部分のリスト、タプルであれば部分のタプルが出力されることが確認されます。

 

リスト同士・タプル同士の足し算ができる

また、リストもタプルも、リスト同士・タプル同士であれば足し算をすることができます。

>>> a = [0,1,2]
>>> c = [3,4,5]
>>> a+c
[0, 1, 2, 3, 4, 5]
>>> b = (0,1,2)
>>> d = (3,4,5)
>>> b+d
(0, 1, 2, 3, 4, 5)

それぞれ足し算によって、要素の連結となります。

 

掛け算ができる

また、タプルもリストも整数による掛け算ができます。
以下のようにタプル・リストに整数を掛け合わせることで、要素列を整数ぶんだけ反復して連結してくれます。

>>> a = [0,1,2]
>>> a*3
[0, 1, 2, 0, 1, 2, 0, 1, 2]
>>> b = (0,1,2)
>>> b*3
(0, 1, 2, 0, 1, 2, 0, 1, 2)

 

相違点

いくつか共通点をご紹介したので、今度は相違点について触れていきます。
これが全てではありませんが、特に意識するべき相違点をご紹介します。

要素追加・削除

まず異なる点として、要素の追加と削除が挙げられます。

リストは以下のように、要素の追加や削除を行うことができます。

>>> a = [0,1,2]
>>> a.append(3) # リストに3を追加
>>> a
[0, 1, 2, 3]
>>> a.remove(3) # リストから3を削除
>>> a
[0, 1, 2]

一方でタプルは、要素の追加や削除ができません

これはタプルがリストとは異なるデータ構造、すなわちリストとは異なる性質を持っているためです。

 

タプル・リストの変換

タプルとリストが別物であることはわかりました。
以下で述べるように、それぞれに利点があるためタプルとリストを変換したいときがあります。

例えばタプルで用意した要素に、新たな要素を追加したいときはリストとして変換する必要があります。

(もちろん、そうならないように設計するのが重要ですが)

これらの変換は以下のようにtuple()とlist()を用いて簡単に行えます。

>>> a=[0,1,2]
>>> a # aはリスト
[0, 1, 2]
>>> b=tuple(a) # aをタプルに変換してbに代入
>>> b # bはタプル
(0, 1, 2)
>>> list(b) # bをリストに変換し直す
[0, 1, 2]

 

速度

タプルとリストが異なる性質を持っているということは、それぞれ得意・不得意があるということです。
簡潔にいうと、タプルは要素の追加や削除ができない代わりに、要素をまとめて取り扱うことに長けており、リストは要素をまとめて取り扱うことはできますが、追加や削除を常に考慮しなければならない分、速度が遅くなります。

具体的にどの程度速度に差が出るのかをみてみましょう。

長さ1000の要素列の連結の速度を見てみます。
要素が同じリストとタプルをそれぞれ二つずつ用意し、100万回連結を行う速度を計測してみます。

>>> import random # 乱数生成用
>>> import timeit # 速度計測用
# 要素列をリストで二つ用意
>>> lsA = [random.randint(0,100) for _ in range(1000)]
>>> lsB = [random.randint(0,100) for _ in range(1000)]
# 同じものをタプルとして用意
>>> tpA = tuple(lsA)
>>> tpB = tuple(lsB)
# 連結する関数を用意
>>> def concat(a,b):
...  c=a+b
...
# 試行回数を設定
>>> loop=1000000
# リストの連結
>>> timeit.timeit('concat(lsA,lsB)', globals=globals(), number=loop)
5.37255421700138
# タプルの連結
>>> timeit.timeit('concat(tpA,tpB)', globals=globals(), number=loop)
4.653372525999657

ご覧の通り、連結はタプルの方が速いという結果になりました。
上に書いた通り、タプルは要素の追加などを考慮しない分、扱う際の速度が速いという利点があります。

一方で要素の追加などはできないため、頻繁に要素を追加するような場合はリストを使う方が良いでしょう。

 

タプルは辞書のキーになる

タプルにできなくてリストにできることは要素の追加と削除ですが、逆にリストにできなくてタプルにできることがあります。
その一つが、辞書のキーになるということです。

例えば’this’,’is’という単語の順番で辞書を作りたい状況を考えます。
この単語列に対して、対応する整数を値として持つような辞書を作ります。

このようなとき、タプルを用いることで単語列をキーとした辞書を実装することが可能です。

>>> a=('this','is') # 2単語の連続でタプル
>>> b=('is','a') # 2単語の連続でタプル 
>>> d={a:10,b:20} # 単語列に対応する数字で辞書を作る
>>> d
{('this', 'is'): 10, ('is', 'a'): 20}
>>> d[('this','is')] # ('this','is')を辞書引きする
10

こうした実装はトライ木などを用いる方が適切ですが、最も安直な実装として覚えておいても良いのではないでしょうか。
特にキーそのものが頻繁に変わるような環境では、木を作り直すよりも速いため重宝します。

 

一方で同様のことをリストで行おうとすると、以下のようなエラーが出てしまいます。

>>> a_lis = list(a) # 2単語の連続で リスト
>>> b_lis = list(b) # 2単語の連続でタプル
>>> d={a_lis:10,b_lis:20}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

 

タプル・リストの連結

連結できるのはリスト同士・タプル同士だけであることに注意が必要です。
いくらリストがタプルの代わりになるとはいえ、リストと

タプルの連結を足し算で行うことはできません。

>>> a = [0,1,2]
>>> b = (0,1,2)
>>> a+b # リストにタプルを連結
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "tuple") to list
>>> b+a # タプルにリストを連結
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "list") to tuple

これは二つが異なるデータ構造であることに起因します。

上で説明したように、タプルとリストはそれぞれ得意不得意が異なるデータ構造であるため、一緒くたに扱うような足し算はできないのです。

これは、以下のように文字列と整数が足し算できないのと同じ理由ですね。

>>> s = 'age' # 文字列(str)
>>> i = 20 # 整数(int)
>>> i+s # 20ageを期待して連結してもエラーになる
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

まとめ

普段あまり意識しないpythonのタプル・リストについてその共通点と相違点の一部をご紹介しました。

この記事では、二つが別物であるということを紹介するのに留めましたが、もし興味があれば中身がどう違うのか、なぜ差が出るのかといった理由を探ると、よりpythonだけでなくプログラミングの理解が深まるかと思います。

 

python
pickleでエラーならdillで保存する!【Python】

Pythonのpickleを使うと、いろいろなデータを保存出来て便利ですよね。 しかし、ファイルオブ …

python
1
【WP REST API解説】投稿を更新する(POST /posts/id)

Word PressのAPIを用いてすでに投稿されている記事を更新する方法について説明します。 あわ …

python
【対処法】pip install mecab-python3のエラー

久しぶりに自然言語処理の環境を一から作るとき、形態素解析器MeCabのインストールでコケることがよく …