読者です 読者をやめる 読者になる 読者になる

Python の tuple のリテラル表記について考える

id:t2y-1979パーフェクトPythonというエントリの一節、

要素が1つの場合には tuple である事が解らなくならないように、(1,) のように末尾にカンマを記述します。

これは少し誤解を招く表現です。というのは、分からなくならないようにこのような記述をするのではなく、tuple の構文はカンマで区切るものであり、カッコは人間が分かりやすいように付けているものだからです。

というのを見て、おや?と思った。
tuple の括弧って「人間がわかりやすいように付けているもの」なんだっけ? と。

たとえばこんな場合

最初に頭に浮かんだのは関数の呼び出しの場合。件のエントリでも例としてあげられているが

>>> type((1,))
<type 'tuple'>

のように関数の引数に tuple を渡す場合は括弧が必須になる。

もし、括弧が「人間がわかりやすいように付けているもの」なので省略可能だとすると type(1,) のようにも書けるはずだ。
だが、実際にこれを実行すると

>>> type(1,)
<type 'int'>

となる。関数呼び出しをする際、末尾のカンマは tuple リテラルの宣言とは解釈されない。
それに func((1, 2, 3), 4) というのを括弧なしで記述するのは難しいだろう。


また、list の中に tuple を入れる場合も括弧は必要とされる。[(1, 2), (3, 4)] と書かないと list の中に tuple を宣言することができない。
字面を見たらそのまんまなんだけど、単にカンマで区切るだけでは tuple と list の境目がわからない。

というわけで、括弧は「機械(パーサ)が解釈する上でも必要なもの」だと思われるので、
「人間がわかりやすいように付けているもの」という意見には違和感を感じる。
そう考えると「tuple の構文は括弧とカンマでくくられたもの。ただし場面によっては括弧は省略できる」と表現するのがよいのではないだろうか。

そもそも多値と tuple リテラルって別物じゃないの?

次に、もうひとつ別のことを考えた。
括弧を省略した tuple って、多値を表すときに利用される syntax sugar なんじゃないか、と。
関数の中で return 1, 2, 3 と書いた時に、返り値として tuple が渡って行くけど、
これは「tuple をリテラルで書いている」のではなくて「多値を表現している」と考えたほうが自然だ。
また、a, b, c = 1, 2, 3 のような多重代入の場面でも「tuple をリテラルで書いている」という意図はないのではないだろうか。

これは、実装の効率や都合の関係で、多値を返す場合や多重代入の実現に tuple を利用しているのではないかという仮説だ。
Ruby でも多値を取り入れる際に、その実装として Array を使っている。(当時、matz は別のものを使おうか悩んでいたようだけど)

憶測を言わずに原典にあたれ

多値と tuple リテラルって別の由来じゃないの? ともにょもにょ考えていたが、
どちらにせよ tuple データが得られるのは変わらないんだし…ともにょもにょ考えていたのだが、
いろいろググっていたら最後に Python の言語リファレンスにたどり着いた。

その一節、オブジェクト、値、および型 の説明にこんな記載がある。

タプル型 (tuple)
タプルの要素は任意の Python オブジェクトにできます。二つまたはそれ以上の要素からなるタプルは、個々の要素を表現する式をカンマで区切って構成します。単一の要素からなるタプル (単集合 ‘singleton’) を作るには、要素を表現する式の直後にカンマをつけます (単一の式だけではタプルを形成しません。これは、式をグループ化するのに丸括弧を使えるようにしなければならないからです) 。要素の全くない丸括弧の対を作ると空のタプルになります。

ちゃんと読むと、個々の要素を表現する式をカンマで区切ってと記載されている。
原文ではTuples of two or more items are formed by comma-separated lists of expressions.という表現。
つまり、Python の tuple の定義は「式をカンマで区切ったもの」ということだ。
この定義には括弧の有無は関係ないようだ。

というわけで、僕の思いついた「多値が tuple 化されるのは syntax sugar で、tuple リテラルとは異なるもの」という仮説は誤りで、
単に return のところに tuple リテラルを書いているというもののようだ。
もちろん、途中で書いた「tuple の構文は括弧とカンマでくくられたもの。ただし場面によっては括弧は省略できる」というのも間違い。

というわけで、僕のひとり相撲は幕を閉じたのであるちゃんちゃん。
結論:ちゃんとリファレンス読みましょう。

おまけ

途中でちゃんと tuple リテラルになってるか調べるため、dis モジュールを使って動きを見たりしてました。
使い方忘れてちょっと戸惑ったので、メモ代わりに載せておきます。

>>> def foo():
...     return 1, 2, 3, 4
...
>>> foo()
(1, 2, 3, 4)
>>>
>>> from dis import dis
>>> dis(foo)
  2           0 LOAD_CONST               5 ((1, 2, 3, 4))
              3 RETURN_VALUE

ちゃんと LOAD_CONST で tuple を読み込んでますね。