先日開催されたPyCon mini SapporoのLT「PHPerが聞きたいPythonのいくつかのこと」で出たこのネタにマジレス。
えーっと、これは「要素がひとつだけのタプルを定義するとき、なんでカンマが必要なのか納得いかない😑」って顔してますね。なんとか納得してもらえるよう説明してみます。
タプルの記法
要素が2つ以上のとき、リストとタプルの記述上の違いはカッコの種類だけです。
>>> lst = ['spam', 'ham', 'egg'] # リスト
>>> tpl = ('spam', 'ham', 'egg') # タプル
しかし要素がひとつだけの場合リストは普通に要素を記述するだけでいいのに、タプルだと要素の後ろにカンマを入れる必要があります。
>>> lst = ['spam'] # カンマは必要ない
>>> type(lst)
<class 'list'>
>>> lst
['spam']
>>> tpl = ('spam',) # カンマが必要
>>> type(tpl)
<class 'tuple'>
>>> tpl
('spam',)
「ホントはカンマいらないんじゃないの?」って思ったら、試しにカンマを省略してみましょう。
>>> tpl = ('spam')
>>> type(tpl)
<class 'str'> # ???タプルじゃない!
>>> tpl
'spam'
タプルじゃなく文字列になってしまいました。なぜでしょう?('spam')
の部分をよく見てください。何か気づきませんか?('spam')
の部分です。よーく見てください。
そうです。('spam')
のカッコは式の評価順を指定するためのカッコなんです。この場合カッコの前後には何もないので、カッコ無しで'spam'
と書いたのと同じ結果になります。
Pythonのタプルの記法がちょっと分かりにくくなってるのは、タプルを表すカッコと式の評価順を表すカッコが混在しているためです。同じ記号なのに文脈によって意味が違ってくるんです。
最初に書いたタプルの例、実は次のようにカッコを省略して記述できます。
>>> tpl = 'spam', 'ham', 'egg' # カッコを省略
>>> type(tpl)
<class 'tuple'>
>>> tpl
('spam', 'ham', 'egg')
ここで重要なのは'spam'と'ham'と'egg'がカンマで区切られているってことです。タプルかどうかを示しているのは実はカッコではなくてカンマの方です。
なお最後の要素の後にカンマを付けても構いません。付けても付けなくても結果は同じです。
>>> tpl = 'spam', 'ham', 'egg', # 最後の要素の後にカンマを付けてみる
>>> tpl
('spam', 'ham', 'egg')
要素がひとつだけの場合は必ず要素の後にカンマを付けなければなりません。理由はわかりますよね。これを省略したらタプルであることを示すものが何も無くなってしまうためです。
>>> tpl = 'spam', # 最後のカンマがタプルであることを示す
>>> type(tpl)
<class 'tuple'>
>>> tpl
('spam',)
タプルを記述するときはカンマが必要、その理由がお分かりいただけたでしょうか。
ただし例外があります。空のタプルはカンマを使わず、カッコだけを記述しなければなりません。
>>> tpl = ()
>>> type(tpl)
<class 'tuple'>
>>> tpl
()
こうゆう分かりにくさが生まれた原因はキーボードから直接入力できるカッコが()、[]、{}しかないこと。タプル用にもう一種類別のカッコが欲しかったんだけど、無いので()を使いまわすしかなかったってことなんだと思います。
タプルの使いどころ
リストとタプルは似ていますが、リストがミュータブル(更新可能)なのに対し、タプルはイミュータブル(更新不能)です。「いやいや、それは分かるけど、リストさえあればタプル無くてもいいんじゃない?更新しなければリストはタプルと同じように使えるんでしょ。リストがあればいいじゃん」って思いました?
でもタプルにはそれなりの存在理由があるんです。ひとつはメモリ消費量です。リストに比べタプルの方がシンプルな実装となっているため、プログラム内で大量のデータを保持する必要がある場合、タプルを使った方が少ないメモリで高速に動作します。
また辞書のキーや集合の要素にリストを使うことはできませんが、タプルはどちらにも使用可能です。
>>> d = {}
>>> d[1, 2] = True # 辞書のキーにタプルを使用
>>> d[1, 3] = False
>>> d
{(1, 2): True, (1, 3): False}
>>> s = {(1, 2), (3, 4), (5, 6)} # 集合の要素にタプルを使用
>>> s
{(1, 2), (3, 4), (5, 6)}
これも実はタプル
複数の変数の値を一度にセットする次のような書き方、Pythonではよく見かけるものです。これも実はタプルです。
>>> x, y, z = 5, 10, 20
右辺は(5, 10, 20)というタプルですね。このタプルから値を取り出し(アンパック)、その値を用いて左辺のタプルを生成(パック)、結果的にx, y, zそれぞれに値がセットされるという仕組みです。