Python の嫌いなところ

ちょっと古いけど。

デブサミ会場で、雑談していると「PythonはDISらないんですか」と柴田さんから。なんか自虐的じゃない?
とは言うものの、Pythonは基本的に良い言語で、 不満な点はかなり微妙だ。一般人にはどーでもいいような点だと思う。
一応リストを。

  • 式と文の区別が明確。
  • インデントベースだとeRubyのようなテンプレートが難しい
  • ブロックを含む式を持てない。Haskellみたいにブレースを使えば式にできるといいのに
  • reduceがなくなる
  • generator難しい。yieldを書くと戻り値が変化するってのはどうよ
  • list comprehensionが難しい。英語人には人気みたいだけど
Matz日記:PythonをDisる。


Python は基本的によい言語」には同意。Rubyist でも、使ってもないのに Python を DIS る人がいるのは残念。インデントベースが嫌いという人がいるけど、それって「'{ }' を使わず 'end' を使うから Ruby 嫌い」と同じぐらいバカげた発言だよ! インデントベースは欠点もあるけど、Ruby のように end の対応が分からなくなることがないからオレは好きだよ!

しかしまつもとさんが挙げたリストは、たしかに欠点というには微妙だ。これらを欠点と思うのは、上級者だと思う。

そこで、自分が日頃思っている、Python の嫌いなところを挙げてみた。


・print 文がよけいな空白と改行を出力してしまう。
Python でいちばん嫌いなのは、print 文が余計なおせっかいをすることだ。改行文字を勝手に追加すんな! 引数の間に勝手に半角空白をつけるな!
print 文のかわりに sys.stdout.write() を使おうとすると、今度は引数を 1 つしかとれない。
Python3000 では print(arg, sep='', end='') とすれば余計な改行も空白も出力されないが、それがデフォルトの設定になっているべきだろ。なんでいちいちオプション指定せなあかんねん!
Ruby はこのへんがよくできてて、余計なことをしない print、よきに計らってくれる puts、デバッグで大活躍の p、と用途に応じて複数の print 系メソッドを用意してくれている。Python も、print と println くらいはあっていいと思う。


・list や dict や str が大文字で始まっていない。
built-in type の list や dict や tuple や str や set や file は、クラス名と同じように最初は大文字にしてほしかった。オレは list や dict や tuple や str や set や file という変数名を使いたいんだよ! list = List() とか dict = Dict() とかいうふうにしたいんだよ! lst や dct や L や D とかを変数名にするのがイヤなんだよ!
これからは Samlltalk 風に aList や aDict を使おうか。


・built-in type のオブジェクトにプロパティが追加できない。
たとえばこんなの。

>>> L = []
>>> L.default = 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'default'

これ、結構不便。


・built-in type にメソッドが追加できない。
たとえばこんなの。

>>> def foo(self):
...     print('foo')
...
>>> list.foo = foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'list'

Ruby だとこんな制限がないから、たとえば Object#to_yaml() や Object#to_json() をあとから追加できる。Python ではこれができないから、to_yaml() や to_json() はメソッドではなく関数にするしかない。


・str#join() が文字列以外の要素があればエラーになる。
たとえばこんなの。

>>> L = ['foo', 'bar', 123]
>>> ''.join(L)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sequence item 2: expected string, int found

str 以外の要素があった場合、Ruby では勝手に文字列に変換されるけど、Python はエラーになる。これもけっこう不便。


・super を使った親クラスのメソッド呼び出しがしにくい
というか、親クラスのメソッドを呼び出すときにクラス名が必要な時点で間違ってる。

class Parent(object):
    def hello(self, name='World'):
        print 'Hello, %s!' % name

class Child(Parent):
    def hello(self, name='World'):
        print "***",
	## 親クラスのメソッドを呼び出す
	Parent.hello(self, name)        # 親クラス名が必要
	## または super を使う
	super(Child, self).hello(name)  # 子クラス名が必要


・dict をループするのに .iteritem() をつける必要がある。
for key, val in dict1.iteritems(): ではなくて for key, val in dict1: で済むようにすべきだろ。Ruby はこうできるようになってるんだよね。Python も dict.__iter__() は dict.iteritems() にすべきだった。だって for val in dict1: よりも for key, val in dict1.iteritems(): のほうが圧倒的に多いもの。


・dict においてキーがないときに例外が発生する。
たとえば d = {'a'=>1} とあったときに、Python では d['b'] で KeyError が発生する (d.get('b') だと None が返される)。他の言語 (Java, Ruby, PHP, Perl) ではどれも NULL や nil が返ってくるので、Python だけ違和感がある。dict[] と dict.get() の仕様を逆にしてほしかった。


・関数のすぐ外側のスコープにある変数に代入できない。
Python では、local 変数と global 変数に対する代入はできる。

x = 0           # global 変数
y = 0           # global 変数
def f():
  global y      # global 変数として宣言
  x = 1         # local 変数への代入
  y = 1         # global 変数への代入
#
f()
print x     #=> 0
print y     #=> 1

しかし、関数の外側のスコープを表すキーワードがないため、関数の外側のスコープにある変数に代入できない。

def f():
    x = 0         # f() の local 変数への代入
    def g():
	x = 1     # g() の local 変数への代入
    #
    g()           # g() を呼び出しても f() の local 変数は変更されない
    print x       #=> 0
#
f()

Ruby の closure に慣れてしまった身としては、outer とかそういうキーワードを追加してでも、関数の外側の変数に代入できるようにしてほしい。あるいは def のかわりに closure を導入するとか。


・変数のスコープの仕様がわかりにくい。
わかりにくいというか、わかりやすい説明をみたことがない。たとえばこんなの。

def foo(self, x):
    print x
deff bar(self, y):
    foo(self, y)     #  これは OK なのに

class Test(object):
    def foo(self, x):
        print x
    def bar(self, y):
        foo(self, y)   # これは NG というのがうまく説明できない

これは今の仕様に不満があるというわけではなくて、Python における変数のスコープの仕様を簡潔にうまく説明できないことが不満ということ。誰かわかりやすい解説を教えてください。


・引数省略時のデフォルト式が定義時にしか評価されない
def f(d={}) という定義があった場合、Ruby では「{}」が関数呼び出しごとに評価されるので、関数呼び出しごとに新しい Hash オブジェクトが生成される。
これに対し、Python では「{}」が定義時にしか評価されないので、dict オブジェクトは定義時に 1 回だけ生成され、それが毎回引数として渡される。Ruby と同じようにしようとすると、Python では次のようにしなければならない。

def f(d=None):
    if d is None: d = {}

どちらが直感的かというと、やっぱり Ruby だよな。


・文字列に式が埋め込めない。
文字列の中に式の値を埋め込む場合、Python では "name: %s, age: %s" % (name, age) とする。しかし文字列が長くなったり、式の数が多くなった場合、非常に見づらいコードになる。
他の言語だと "name: #{name}, age: #{age}" (Ruby) や "name: $name, age: $age" (PHP) のように文字列の中に式を直接埋め込むことができる。特に Ruby では #{} の中に任意の式を埋め込むことができる。素晴らしい。
Python でも、例えば e"name: #{name}" のように新しい prefix を用意すれば埋め込み式をサポートできると思うんだけど、どうでしょう?


・3 項演算子が書きにくくて読みづらい。
3 項演算子は他の言語だと condition ? truevalue : falsevalue と書くけど、Python では truevalue if condition else falsevalue と書く。これって書きにくいし読みづらくない? (慣れの問題かもしれないが)。
Python ではなるべく記号を使わないようにしているみたいだけど、そのせいで読みづらくなるのはちょっとなー。


正規表現を表すリテラルがない。
まあ Python には raw string があるから、Java とは違って書くのはそう困らないんだけど、やっぱりリテラルとしてあったほうが使い勝手がよい。そのためには re モジュールが組み込みになる必要があるけど、きっとその予定はないだろう。


・repr() が貧弱。
Ruby の Object#inspect() や PHP の var_dump() と比べると、Python の repr() は貧弱である。特にインスタンス変数の値を表示してくれないというのは、大変不便である。
ついでにいうと、Ruby の pp に当たるライブラリも標準で用意してほしい。


・起動が遅い。
Python プロセスの起動は遅い。はっきりいって、CGI ではまず性能は出ない。原因はよく知らないので、知っている人がいれば教えてください。
ただ、mod_python の性能は素晴らしくよい。測定してみた限りでは、PHP よりも性能がよい。


・ファイルをロックする機能が Windows ではサポートされてない。
ファイルをロックするのは、UNIX 系だと import fcntl して fcntl.flock(file.fileno(), fcntl.LOCK_EX) でてきるんだけど、Windows ではこれができない。Ruby だと Windows でも使えるだけでなく、File クラスに flock() メソッドが定義されているから使うのも簡単。


・標準の CGI ライブラリに Session 機能がついていない。
これはどうにかしてほしい。言語仕様の拡張や変更がいらないので、すぐにでもできるとおもうんだけどなー。まあ Rubycgi.rb もどうかと思うけど。



python の好きなところは後日。


それから、「self.」が多いのを嫌う人がいるけど、はっきりいってこれは慣れ。PHP の「$this->foo」や Perl の「$this->{foo}」に比べればずいぶんましだし、JavaScript だって「this.」が必須だけどみんな文句も言わず使っているよね? Python の「self.」だけ嫌うのはおかしいよ。


まあ結局は Ruby の「@foo」がよくできてるということなんだけどな!