Python基礎講座 #9 タプル (Tuple)

読了 8分

今日の講座ではタプルについて勉強していきましょう。

タプルは前回の講座で学んだリストととても似たデータ型です。そのためか、ほとんどの方がリストとタプルの違いはご存知ですが、いつリストを使うべきで、いつタプルを使うべきか分からない方が多いようです。

これからリストとタプルの違いを見ていき、リストの代わりにいつタプルを使うべきかを見ていきましょう。

まず、タプルを定義する方法を見ていきましょう。

リストはブラケットを使って定義しますが、タプルは括弧を使って定義します。例を見ながら説明していきます。

括弧を使ってタプルを一つ定義してみましょう。

>>> t1 = (1, 2, 3)
>>> t1
(1, 2, 3)

type関数を使ってデータ型を確認してみましょう。

>>> type(t1)
<class 'tuple'>

データ型がタプルであることを確認しました。

タプルを定義する時は括弧を省略することもできます。

括弧を使わずに定義してみましょう。

>>> t2 = 1, 2, 3
>>> t2
(1, 2, 3)

もう一度データ型を確認してみましょう。

>>> type(t2)
<class 'tuple'>

括弧を使った時と同じようにタプルが作られていることが確認できました。

リストとは違って、タプルを定義する時には注意点が一つあります。タプルの中に定義されるアイテムが一つだけの時は、アイテムの後に必ずカンマを入力しなければなりません。

まず一つのアイテムを持つリストを定義してみましょう。

>>> l = ['one']
>>> l
['one']

リストはこのように問題なく定義されましたが、タプルの場合はどうなるか確認してみましょう。

>>> t = ('one')
>>> t
'one'

何かおかしくないですか?括弧が見えませんね???

このオブジェクトのデータ型を確認してみましょう。

>>> type(t)
<class 'str'>

タプルではなく文字列オブジェクトが作られていますね。先ほど申し上げた通り、アイテムが一つだけのタプルを定義する時は、次のアイテムがなくても最後にカンマを入力しなければなりません。

>>> t = ('one',)
>>> t
('one',)

今度は括弧が見えていることが分かります。本当にタプルが定義されているか確認してみましょう。

>>> type(t)
<class 'tuple'>

先ほど、タプルはリストととても似たデータ型と申し上げましたが、どんな点が似ているか確認してみましょう。

タプルはリストと同じく、順序を持つシーケンス型です。したがって、リストと同じくインデックスを使うこともできますし、スライシングをすることもできます。

タプルを一つ定義した後、インデックスを使って最初のアイテムを出力してみましょう。

>>> t = 1, 2, 3, 4, 5
>>> t[0]
1

今度はスライシングを使って最初の3個のアイテムを出力してみましょう。

>>> t[:3]
(1, 2, 3)

タプルもまたリストと同じくイテレータオブジェクトなので、for loopを使うことができます。

iter関数を使って、tupleがイテレータオブジェクトかどうか確認してみましょう。

>>> iter(t)
<tuple_iterator object at 0x000002DBC4985640>

イテレータオブジェクトであることを確認したので、for loopを使ってみましょう。

>>> for i in t:
...     print(i)
...
1
2
3
4
5

今度はタプルとリストの違いについて見ていきましょう。

一つ目の違いです。

リストはすでに定義されているアイテムの値を変更できますが、タプルは一度定義されたアイテムの値を変更できません。C、C++、Goなどの言語で使われる定数(コンスタント)に似ていると考えてください。

まずリストを定義した後、アイテムの値を変更してみましょう。

>>> my_list = [1, 2, 3]
>>> my_list[0] = 'one'
>>> my_list
['one', 2, 3]

今度はタプルを定義して最初のアイテムの値を変更してみましょう。

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

タプルオブジェクトはアイテムアサインメントをサポートしないという TypeError が発生しました。

二つ目の違いです。

リストは新しいアイテムを追加したり、既存のアイテムを削除したりできます。しかしタプルは定義された瞬間にメモリ上の長さが固定されてしまうため、新しいアイテムを追加したり、既存のアイテムを削除したりすることができません。

まずリストを定義した後、新しいアイテムを追加してみましょう。

>>> my_list = [1, 2, 3]
>>> my_list.append(4)
>>> my_list
[1, 2, 3, 4]

新しいアイテム4が追加されました。

追加したアイテムをもう一度削除してみましょう。

>>> my_list.remove(4)
>>> my_list
[1, 2, 3]

新しく追加したアイテムが削除されました。

removeメソッドを使う時は、削除するアイテムのインデックスを使うのではなく、削除したいアイテムの値を入力しなければならないことを覚えておいてください。もしインデックスを使いたい場合は、removeメソッドの代わりにpopメソッドを使ってください。

今度はタプルを定義して新しいアイテムを追加してみましょう。

>>> my_tuple = 1, 2, 3
>>> my_tuple.append(4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'append'

タプルオブジェクトはappendアトリビュートを持っていないというエラーメッセージが出力されました。タプルはアイテムの追加、削除ができないため、appendやremoveメソッドを持っていません。

それではタプルクラスにはどんなメソッドが定義されているか、help関数で確認してみましょう。

>>> help(tuple)
Help on class tuple in module builtins:

class tuple(object)
|  tuple(iterable=(), /)
|
|  Built-in immutable sequence.
|
|  If no argument is given, the constructor returns an empty tuple.
|  If iterable is specified the tuple is initialized from iterable's items.
|
|  If the argument is a tuple, the return value is the same object.
|
|  Built-in subclasses:
|      asyncgen_hooks
|      UnraisableHookArgs
|
|  Methods defined here:
|
|  __add__(self, value, /)
|      Return self+value.
|
|  __contains__(self, key, /)
|      Return key in self.
|
|  __eq__(self, value, /)
|      Return self==value.
|
|  __ge__(self, value, /)
|      Return self>=value.
|
|  __getattribute__(self, name, /)
|      Return getattr(self, name).
|
|  __getitem__(self, key, /)
|      Return self[key].
|
|  __getnewargs__(self, /)
|
|  __gt__(self, value, /)
|      Return self>value.
|
|  __hash__(self, /)
|      Return hash(self).
|
|  __iter__(self, /)
|      Implement iter(self).
|
|  __le__(self, value, /)
|      Return self<=value.
|
|  __len__(self, /)
|      Return len(self).
|
|  __lt__(self, value, /)
|      Return self<value.
|
|  __mul__(self, value, /)
|      Return self*value.
|
|  __ne__(self, value, /)
|      Return self!=value.
|
|  __repr__(self, /)
|      Return repr(self).
|
|  __rmul__(self, value, /)
|      Return value*self.
|
|  count(self, value, /)
|      Return number of occurrences of value.
|
|  index(self, value, start=0, stop=9223372036854775807, /)
|      Return first index of value.
|
|      Raises ValueError if the value is not present.
|
|  ----------------------------------------------------------------------
|  Class methods defined here:
|
|  __class_getitem__(...) from builtins.type
|      See PEP 585
|
|  ----------------------------------------------------------------------
|  Static methods defined here:
|
|  __new__(*args, **kwargs) from builtins.type
|      Create and return a new object.  See help(type) for accurate signature.

一番上の部分にタプルクラスはBuilt-in immutable sequenceという重要な説明があり、一番下の部分を見ると、countとindexメソッドだけが定義されているのが分かります。

この二つのメソッドを使ってみましょう。まずcountメソッドを使って、タプルアイテムの中で同じ値を持つアイテムが何個あるか確認してみましょう。

>>> t = 1, 2, 2, 2, 3
>>> t.count(2)
3

2の値を持つアイテムが3個あることが確認できました。

今度はindexメソッドで、3の値を持つアイテムのインデックスを確認してみましょう。

>>> t.index(3)
4

3の値を持つアイテムのインデックスは4であることが確認できました。

タプルとリストの違い、3つ目です。

タプルはリストよりも少ないメモリを使います。まず同じアイテムをリストとタプルに保存して、それぞれが使っているメモリのサイズを確認してみましょう。

my_list = ['one', 1, 1.2345, True]
my_tuple = 'one', 1, 1.2345, True

まずリストが使っているメモリサイズを確認してみましょう。

>>> import sys
>>> sys.getsizeof(my_list)
120

今度はタプルが使っているメモリサイズを確認してみましょう。

>>> sys.getsizeof(my_tuple)
72

アイテムが数個しかないのに大きな違いがあることが分かります。もしアイテムの数が数百万個だったとしたら、その差はとても大きくなりますよね?

四つ目の違いです。

タプルはリストよりも速く生成されます。タプルはメモリ上に一つのブロックとして保存されますが、リストは二つのブロックにそれぞれオブジェクトに関する情報とデータが保存されるため、タプルよりも生成速度が遅くなります。

実際にタプルとリストのオブジェクト生成速度を比較してみましょう。まずリストが生成される時間を測定してみましょう。

# 下のコードはipythonまたはjupyter notebookで実行してください。
In [1]: %timeit my_list = [1, 2, 3, 4, 5]
46.9 ns ± 0.933 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

今度はタプルが生成される時間を測定してみましょう。

# 下のコードはipythonまたはjupyter notebookで実行してください。
In [2]: %timeit my_tuple = 1, 2, 3, 4, 5
15.5 ns ± 0.65 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

タプルの方が3倍ほど速く生成されることが分かります。

五つ目の違いです。

タプルはリストに比べて、インデックスを使ってアイテムにアクセスする速度がより速いです。

実際にリストとタプルのインデキシング速度を測定してみましょう。

In [3]: %timeit my_list[0]
37.7 ns ± 1.93 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [4]: %timeit my_tuple[0]
36.3 ns ± 1.16 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

リストとタプルのインデキシング速度はほぼ同じであることが分かります。

最後に、これまで学んだタプルの長所と短所をまとめて、リストの代わりにいつタプルを使うべきかを見ていきましょう。

タプルはリストに比べて少ないメモリを使い、生成が速いです。そしてインデックスを使ってアイテムにアクセスするのにかかる時間がリストに比べて短いということを学びました。しかし、タプルはアイテムの値を変更したり、アイテムを追加または削除することができません。そして、sorted関数を使ってアイテムを並べ替えることもできません。

これでリストの代わりにいつタプルを使うべきか、お分かりいただけましたよね?

オブジェクトを定義した後、アイテムの値を変更しなければならない時、新しいアイテムを追加しなければならない時、アイテムを順番に並べ替えなければならない時はリストを使えば大丈夫です。逆に、一度定義されたデータが変更されてはいけない場合や、データを変更する必要がない時には、常にメモリを少なく使い、パフォーマンスがより良いタプルを使えば大丈夫です。

今回の講座はここまでにして、次回の講座では非常に重要なデータ型である辞書について勉強しましょう。

X