# リストとタプルの
違いってなに?

リストは変更できる。
タプルは変更できない。
Hello, world!

# 1. 機能の違い

# 1.1. 変更できない

リストは変更できるので、問題なくこれが実行できますが

lst = [0, 1, 2]
lst[0] = 3

タプルは変更できないので、変更しようとすると TypeError になります。

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

「変更できない」と言うのを難しい言葉でイミュータブル immutable と言います。 タプル tuple はイミュータブル immutable です。

「変更できる」と言うのを難しい言葉でミュータブル mutable と言います。 リスト list はミュータブル mutable です。

# 1.2. メソッドの数の違い

以下のコードをコピペして実行して見てください。

#
# 対話モード >>> に
# コピペで実行できます。
#
def normal_attributes(cls):
    for attribute in dir(cls):
        if not is_special_attribute(attribute):
            yield attribute

def is_special_attribute(attribute):
    return all((
        attribute[:2] == '__',
        attribute[-2:] == '__'
    ))

print(*normal_attributes(tuple), sep="\n")
print(*normal_attributes(list), sep="\n")

するとタプルは変更できないのでメソッドが、たったの2つしかありませんが、

... 
>>> print(*normal_attributes(tuple), sep="\n")
count
index
>>> 

反対にリストは変更できるため、その操作に合わせてメソッドがたくさんあることがわかります。

>>> print(*normal_attributes(list), sep="\n")
append
clear
copy
count
extend
index
insert
pop
remove
reverse
sort
>>>

# 2. どっちを使えばいいの?

タプルとリスト、どうやって使い分ければいいのでしょうか?

なるべくタプルを使った方が良いのかなと個人的に考えています。 それは以下2つの理由からです。

# 2.1. 副作用がないから

変更できないことに意味があるのでしょうか? ざっくり言えば、「可読性」と「安全性」が上がります。 変更ができないことは「可読性」と「安全性」をあげると言われているのを見かけます。

たかだか変更できないくらいのことに、どれくらいの意味があるのかは、 以下の記事で見てきました。

# 2.2. KISS の原則に沿っているから

自由に変更ができる多機能なリストを使っていた方が、拡張性が良いような気がします。 この点についてはどうでしょうか?わざわざ機能を制限する必要性があるのでしょうか?

「依存性逆転の原則」、「継承より合成」と言う経験則があります。 この2つの言葉を考えた時に使わない機能は、追加しないというのが最近の流行りなのかなと思われます。 この2つの経験則の背景には KISS の原則があるのかなと思っています。

KISS の原則 (英: KISS principle) とは、 「Keep it simple stupid.」(シンプルで愚鈍にする)、 もしくは「Keep it simple, stupid.」(シンプルにしておけ!この間抜け)、 もしくは「Keep it short and simple.」(簡潔に単純にしておけ)という内容の、 1960年代の米国海軍において言われた、経験的な原理・原則[1]の略語。 その意味するところは、設計の単純性(簡潔性)は成功への鍵だということと、 不必要な複雑性は避けるべきだ、ということである。

ただ、絶対に正しいわけではなく、どっちでもよくて迷った場合は、 タプルの方が機能が少なく拡張性に乏しいという不安はあるものの、 とりあえず、タプルを使った方が良いのではないかなと思っている程度の話です。

# 3. タプルの由来

tuple は、言うなればユーザ定義クラスの一種です。 わざわざユーザ定義クラスを作るほどではないなという時に使います。  タプルtuple  で厳しくなったら  辞書dict  を用います。 辞書 dict でも厳しくなったら  ユーザ定義クラス class   を定義します。

# 1. タプルの本来の意味(タプルから辞書、辞書からクラス)
class Person(object):
    def __init__(self, name, age):
        self.name, self.age = name, age

person1 = ('岩倉玲音', 14)                 # 1. tuple
person2 = {'name': '岩倉玲音', 'age': 14}  # 2. dict
person3 = Person('岩倉玲音', 14)           # 3. class

person1[0]                                 # 1. tuple
person2['name']                            # 2. dict
person3.name                               # 3. class

なぜタプルとリストという別のデータ型が用意されているのですか?
 リストとタプルは  、 多くの点で似ていますが、一般には本質的に  異なる方法で使われます   タプルは、Pascal のレコードや C の構造体と同様なものと考えられます。  型が異なっても良い関連するデータの小さな集合で、グループとして演算されます。 例えば、デカルト座標は 2 つや 3 つの数のタプルとして適切に表せます。

一方、  リストは、もっと他の言語の配列に近いものです。  全て同じ型の可変数のオブジェクトを持ち、それらが一つ一つ演算される傾向にあります。 例えば、 os.listdir('.') はカレントディレクトリ内にあるファイルの文字列表現のリストを返します。 この出力を演算する関数は一般に、ディレクトリに一つや二つの別のファイルを加えても壊れません。

ABC 言語の Compounds 型に由来しているらしいです。 PUT value IN name で代入になります。 lst[0] のような添字表記 subscription で参照することはできなかったみたいです。

Compounds - ABC: Some Simple Examples

Compounds are like records or structures, but without field names:

>>> PUT ("Square root of 2", root 2) IN c
>>> WRITE c
("Square root of 2", 1.414213562373095)
>>> PUT c IN name, value
>>> WRITE name
Square root of 2
>>> WRITE value
1.414213562373095

書籍 Fluent Python 2.11 参考文献の末尾にあった「タプルの秘密」から知りました。

こうした制限は、コンパウンド型の主たる目的を明確にしています。 つまりそれは、フィールド名のないただのレコードではないか。 私はそうGuidoに話しました。 彼は「タプルをシーケンスみたいに動作させるようにするのはハックだったよ」と答えました。
タプルの秘密 - Fluent Python

タプルという言葉そのものの意味はこちらになります。数学から引っ張ってきた用語だったんですね。

タプル - Wikipedia
タプルまたはチュープル(英: tuple)とは、複数の構成要素からなる組を総称する一般概念。 数学や計算機科学などでは通常、順序付けられた対象の並びを表すために用いられる。

# 4. 型アノテーション

この辺りの雰囲気は 型アノテーション を使うと伝わりやすいかもしれません。 もしかしたら、伝わらないかもしれません。 知らなくても大丈夫なので気楽に読み流してください。

なお、この辺りの tuple に関する挙動は、 以下の記事を参考にさせていただきました。 大変勉強になりました。ありがとうございます。

このコードを...

lain  = ('岩倉玲音', 14, False)
print(lain)

yaruo = ('やる夫', 20)
print(yaruo)

型付けすると、こんな感じになります。

#
# 新しい型を定義します。
#
from typing import NewType
Name   = NewType('Name',   str )
Age    = NewType('Age',    int )
Gender = NewType('Gender', bool)

from typing import Tuple
Person = NewType('Person',
    Tuple[
        Name,
        Age,
        Gender
])

#
# 変数を定義します。
#
lain:  Person
yaruo: Person

#
# 変数に代入します。
#
lain = Person((
    Name('岩倉玲音'),
    Age(14),
    Gender(False)
))
print(lain)
# ('岩倉玲音', 14, False)
#     すごそうなことをしているように見えますが...
#     変数 lain に代入されているのは
#     たんなる tuple でしかありません。
#     型を明示しないと mypy に弾き返されます。

yaruo = Person((
    Name('やる夫'),
    Age(20),
    # Gender(True),  <--- 1つ抜いておくと...
))
print(yaruo)

mypy をインストールして型検査をして見ましょう。 上記のコードは sample.py として保存しているものとします。

pip install mypy
mypy sample.py
python sample.py

mypy で型検査するとエラーで怒られます。 Person は要素が3つ必要なのに1つ足りないよ、と怒られています。

$ pip install mypy
$ mypy sample.py
sample.py:23: error:
Argument 1 to "Person" has incompatible type "Tuple[Name, Age]";
expected "Tuple[Name, Age, Gender]"
$

ちなみに実行はできます。

$ python sample.py
('岩倉玲音', 14, False)
('やる夫', 20)
$

# 5. まとめ

タプルはイミュータブルなリストです。 イミュータブルにすることは、 コードの可読性や安全性を向上させます。

また Python の tuple は簡易的なクラスを作成することを意図していた ABC 言語の Compounds 型に由来しています。

Last Updated: 12/13/2019, 10:12:44 PM