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

Pythonが他の言語に似てないからって、そんな…

Python

はじめてのひき - PythonSucks
が気になった。

raw_input("hoge") とかで出力もできる

まともな標準入出力は

import sys
sys.stdout.write("Hello, world!\n")

めんどくさいなぁ。

Pythonは標準入出力の存在を前提としない言語だから。PerlRubyとはちょっと文化的に違ってて、むしろ、stdio.hがあるかないかわからないC言語だと思うと納得できる。組み込まれる環境によっては、printとraw_inputは、標準入出力で実装されない可能性もあるかも。JavaのSyetem.out.printとどっこいどっこい。

引数

>>> len("hoge")
4
>>> len("hoge",)
4

1要素タプル

空タプルは () 。 2要素タプルは (1, 2) 。 1要素は (1,)
タプルと % 演算子

ごく稀にびっくりする

>>> "%s" % [1,2]
'[1, 2]'
>>> "%s" % (1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: not all arguments converted during string formatting

Pythonの文法はぶら下げカンマの許可を積極的に定義している。

expression_list ::= expression ( "," expression )* [","]

たぶん、これを意識するだけで、だいぶ驚かずに済むと思う。
ぶら下げカンマ許可な文法って、末尾要素の周辺を編集するとき、影響する行が最小化するので、管理しやすくて嬉しい。学問的には欠点でも、実用上は長所だといえる。実用性が優先するのはLLとしては正しい。

hoge = [
           1,
           2,
           3,
       ] #便利 3の行だけ消せるし、4の行を挿入しても3,の書き換えが要らない

これで、末尾カンマぶら下げの関数呼び出しは、むしろ便利な機能だとわかる。

また、2要素タプルは正式に 1,2, でも 1,2 でも良いのだと知れば、1, を自然に発想できると思う。

タプルにとって括弧記号は、リテラルの開始と終端ではなく、ただの演算順序制御でしかない。たまたま、カンマの結合は他の演算子よりずっと弱いので、括弧で囲むことが多いだけ。

a = 1,
b = 2, 3

% 演算子の右辺は、基本はタプルじゃないといけないんだけど、もしタプルでなかったら、オマケで1要素のタプルということにしといてもらえる。

"%s" % [1,2]

のほうはオマケで、より正確に書くとホントはこうなる

"%s" % ([1,2],)

ということを意識していれば、

"%s" % (1,2,)

がエラーになってもなんとも思わない。

タプルをイミュータブルなリストだと学習してしまうと、不自然に感じる箇所はあるかもしれないけど、リストとタプルはまったく別ものだと学習すると、ひっかかることが少ないように思う。C言語に対して、リストは配列を可変長配列にしたもの、タプルは文法の高度化(値の概念を複素数のように拡張できるもの)という解釈が正だと思う。Pythonのタプルに似たものを持つ言語はあまりないので、理解されにくいのは否めない。

del と len
これらが obj.del や obj.len で無いという文句も定番。 del 予約語は 3000 で消えるらしい。

lenとdel以外にも、mapやfilterについても同じように考える必要があると思う。特定の種類のオブジェクトにしか関連しないメソッドという意味では、lenと立場は一緒だし。
lenについて言えば、長さを実装しないオブジェクトを誤って計測したときのことを考えると、納得できなくもない。

>>> len(None)
TypeError: object of type 'NoneType' has no len()

という「オブジェクトに長さ概念がない」という例外が、もし、

>>> None.len()
AttributeError: 'NoneType' object has no attribute 'len'

という、「属性の名前が間違っている」という例外になってしまうと、意味的に例外を捕捉しにくいという考えじゃないかと思う。
lenだと違いがわかりにくいので、さらにmapで考えると、

>>> map(lambda x:x, None)
TypeError: argument 2 to map() must support iteration
>>> None.map(lambda x:x)
AttributeError: 'NoneType' object has no attribute 'map'

後者だとNoneの部分に入る値ではなく、mapのスペルを疑いそうになる。前者のほうがいい。

Pythonのlen/map/filterは、ビルトイン関数として「文法の一部として必須な要素」と考える。オブジェクトの属性になると、その機能を保証する層が「言語」から「標準クラスライブラリ」になってしまい、言語仕様として「必ずある」とは言えなくなる。
PythonJavaRubyと違って、原始的なオブジェクトに大きな仕様を詰め込みたくないのかもしれない。あるいは、オブジェクト指向を学習する前に、ある程度プログラミングできることを配慮しているのかもしれない。
この方針の差を理解するには、Pythonが言語仕様だけを取り出して組み込みに使うことを念頭に置いた言語だということとか、そもそも学習目的だった言語だということが助けになるかも。

とはいえ、自分も長く疑問に感じていたので、あまりこの推測には自信がない。なにか別の理由もあるかもしれない。

ちなみに、Javaの場合は、スペルミスをコンパイラが型チェックすること、また、オブジェクト指向ありきで文法が成り立っていることから、lengthやsize()はオブジェクトの属性であるほうが望ましい。

一つのことは一つの方法で

なじみやすさ、学習コストの低さを念頭に置くPythonにとっては、より自然な表現が可能な構文を設けるのは、自然な流れじゃないかと思う。

読みやすいけど冗長なコードが書けることと、端的だがハッキッシュなコードが書けることは、動作結果が同じだからといって、けしてredundantではないと思う。「一つのことは一つの方法で」といったって、程度問題で、あらゆる状況にそれを言い出すと、「無名関数とif文があるから三項演算子は要らない」ということになる。

というわけで、

リスト内包表記と map

>>> [x+1 for x in [1,2]]
[2, 3]
>>> map(lambda x: x+1, [1,2])
[2, 3]

これがまさにそうかな、と。map/fliterを使うと、関数を切り出して変数で取り回せるし、そのときクロージャが束縛する変数も、遠いところにあるものになる。ただ、そこまでの使い手じゃない人でも、map/filterの威力を使い、より綺麗に書くことができるとしたら、存在意義がないわけじゃない。

ちなみに、やり方が複数あるのが嫌な人は、こっちのほうが耐えられないかもしれない。

age >= 20 and 'adult' or 'child' #2.4以前
dict(True='adult', False='child').get(age >= 20) #2.4以前
# ↑↓等価
'adult' if age >= 20 else 'child'

def func():
    pass
decorator(func)
# ↑↓等価
@deforator #2.4
def func():
    pass

クォート
"hoge\n" と 'hoge\n' は同じ意味。

Googleでは、メッセージ文字列はダブルクォートで、システマチックなシンボルはシングルクォートで囲むと聞いた。動作が全く同じなら、自分で勝手に意味づけして書き分けできる。また、初心者に細かい違いについて説明する時間を省ける。

not in 演算子

>>> 2 not in [1,2]
False
>>> not 2 in [1,2]
False

is not 演算子

>>> type("") is not str
False
>>> not type("") is str
False
>>> type("") not is str
  File "<stdin>", line 1
    type("") not is str
                  ^
SyntaxError: invalid syntax

これは単に、a != b か、 !(a == b) の違いじゃないのかと…。特に悪い部分はないように見える。
単に「演算子のくせに2単語も使う」ように見えるのが、他のプログラム言語の視点から見て、感覚的に気持ち悪いのだと思う。Rubyですら、2単語演算子を作れない仕様だったりするし。ただ、SQLにはもっと前から hoge IS NULL と hoge IS NOT NULL があるので、(andやorなど)演算子に英字を使う言語で、英語の語感を尊重するのは間違っていない。

self
self 多い。

慣れの問題なので、我慢しないと。

メソッドの機構を関数プログラミング言語の関数を利用して実現するには、selfのような存在はどうしてもはずせない。thisを省略できる言語から見ると、selfが面倒なのはわかるけど、JavaScriptの「関数リテラルでのthis問題」を考えると、selfを明示することをインターフェース仕様として強制できるPythonのほうが安心。
メソッドを変数に取り出して関数のように取り回せる(Rubyとの最大の違い)ことを活かしたプログラムを書くと、感想は変わると思う。
とか言いながら、自分もまだそこまで書いてないけど、selfが省略されない意味がそこにあるというのは、なんとなく確信している。


自分の考えと違うのはこのぐらい。あと細かいのは同意見だったり、新発見だったり。とくに、printの末尾カンマの仕様については、私も違和感を感じる。基本、「ぶら下げカンマはあってもなくてもいい」なのに、print文の構文のカンマだけそのルールと矛盾するのはどうかと。