Python の好きなところ
Python の好きなところ。主に Ruby との対比。
微妙なものも混ざっているけど、ご容赦ください。
・キーワード引数を装備している。
これは Ruby と比べて大きなアドバンテージ。Ruby の、Hash を使った疑似キーワード引数も悪くはないんだけど、使用できるキーワードが引数に明示されないので readability が低い、キーワードを間違ってもエラーにならない、という欠点がある。やはり Ruby でも本物のキーワード引数が欲しい。
しかしキーワード引数を持っている言語って、そう多くはないんだよね。Ruby だけでなく、Perl も PHP も JS も持っていない。もう 21 世紀なんだから、これからの言語は持っていて欲しいな。
・仕組みが単純である。
Python は、全体的に仕組みが単純である。たとえばメソッドやインスタンス変数に対して public, protected, private といったアクセス制御の仕組みは持っておらず、単に「公開したくないメソッドやインスタンス変数は頭に '_' をつけましょう」という了解で済ませている。
変数のスコープも基本的に global と local しかなくて、でもそれで困らないようにできている。
またメソッドは「変数に代入された関数」でしかないので、たとえば Ruby でいう alias は、単にメソッドを別の名前の変数に代入するだけで済む。つまり Ruby では言語仕様として用意しなければならない機能も、仕組みが単純な Python ではそんな必要がない。
class Foo: def hello(self, name): print "Hello, %s!" % name ## alias hello2 = hello
このように、Python は全体的な仕組みがとても単純にできている。それだけでなく、機能も洗練されている。Ruby のほうは、洗練されているという点では Python を超えていると思うけど中の仕組みはずっと複雑である。また PHP は仕組みは単純だけどいかんせん洗練されていない。Python は単純さを保ったまま洗練されているので、非常に好感が持てる。
・'{ }' や 'end' がいらない。
Python やったあとに Ruby をやると、'end' がいくつも出てくるのがなんか目障りに思えてくる。また他の言語では '{ }' や 'end' の対応を間違えると見つけるのが面倒なんだけど、Python ではそれがない。これは Python の大きなアドバンテージだと思う。
ただ、インデントベースなので code generation には弱い。
・関数が first class object である。
つまり、関数を変数に代入したり、引数に渡したりできるということ。関数型言語や JavaScript ではおなじみの機能。これは確かに便利。
Ruby にはこの機能がないけど、ブロックと Proc があるのでそうは困らないし、Ruby へ導入するのは難しいだろう。
・関数デコレータがある
関数が first class object であるおかげで、関数デコレータを使ってメソッドにいろいろ属性情報をもたせることができる。
たとえば TurboGears フレームワークでは、コントローラでどれがアクションになるかを関数デコレータを使って指定している。テンプレートの指定もわかりやすい。
## http://www.python.jp/Zope/workshop/200706/turbogears.pdf より引用 @expose(template="tgbbs.templates.bbslist") def index(self): art_list = [] for a in Article.select(): art_list.append(a) return { 'art_list':art_list }
これは、宣言的なプログラミングをするときに重宝するはず。たとえば DbC のように事前条件や事後条件を記述したい場合は、まさにピッタリの機能だ。
・prototype base によく似ている
Python は class base だけど、JavaScript のような prototye base にとてもよく似ている。Python も JavaScript もメソッドの実体は関数だけど、そういう言語では prototype base のほうが自然なんだろう。
たとえば instance object ごとにメソッドの動作を変えるなんてことが、特に難しいことを考えずにできる。
class Foo: def __init__(self, value): self.value = value if self.value % 2: self.show = self._show_odd # メソッドを変更 else: self.show = self._show_even # メソッドを変更 def _show_odd(self): print "%s is odd." % self.value def _show_even(self): print "%s is even." % self.value Foo(1).show() #=> 1 is odd. Foo(2).show() #=> 2 is even.
Ruby でも singleton method を使えば同じことができるけど、ここでも Ruby がそれ用の言語仕様を必要とするのに対し、Python では単に代入で済んでおり仕掛けが実にシンプルだ。
・Generator がある
Python を勉強しはじめたとき、Generator が何のことかさっぱり分からなかったけど、一度理解するとこれはすごい機能だと思った。Generator は、簡単にいうと「関数の実行を一時的に中断して呼び出し元に戻り、また呼ばれたときは同じ場所から実行を再開する」ということなんだけど、イメージ的にはドラクエや FF でセーブして、また同じところからゲームを再開するのと似ている。
つまりは、現在の実行状態を (部分的にとはいえ) 保存して変数に代入したりできるわけで、Seaside が継続を使ってやろうとしたことが Python だと Generator を使ってできるんじゃないかと思う。あとワークフローのライブラリも、Generator を使うと素直に実装できそう。
なんか、想像力しだいでいろんなことができそうな機能なんだよね、Generatorって。
・内部情報にアクセスできる。
たとえば次の例では関数についての内部情報にアクセスしている。
# -*- coding: utf-8 -*- delta = 1 def f1(x, y): """example function.""" ans = x + y + delta return ans ## ファイル名 print repr(f1.func_code.co_filename) #=> 'hoge.py' ## 開始行番号 print repr(f1.func_code.co_firstlineno) #=> 4 ## 関数名 print repr(f1.func_code.co_name) #=> 'f1' ## グローバル変数名 print repr(f1.func_code.co_names) #=> ('delta',) ## ローカル変数名 print repr(f1.func_code.co_varnames) #=> ('x', 'y', 'ans') ## ローカル変数の数 print repr(f1.func_code.co_nlocals) #=> 3 ## 引数の数 print repr(f1.func_code.co_argcount) #=> 2 ## バイトコード print repr(f1.func_code.co_code) #=> '|\x00\x00|\x01\x00\x17t\x00\x00\x17}\x02\x00|\x02\x00S' ## 必要スタックサイズ print repr(f1.func_code.co_stacksize) #=> 2
こういった情報が何の苦もなく取ってこれるので、Python ではメタプログラミングでゴニョゴニョしやすい。
・0 < val < 10 と書ける。
他の言語だと 0 < val && val < 10 と書かなければいけないところを、0 < val < 10 と書くことができる。これはたとえば 0 < self.heavyweight(123)[-1] < 10 のように、式の値が複雑な場合には特にうれしい。
・内包表記で繰り返しと選択が同時に指定できる。
Python の内包表記と Ruby の Enumerable を比べると、内包表記では繰り返しと選択が同時に指定できるので、Ruby のよりも実行効率がいい。
## Python (繰り返しは 1 回だけ) [ x*x for x in numbers if 1 <= x <= 9 ] ## Ruby (繰り返しが 2 回ある) numbers.select {|x| 1 <= x && x <= 9 }.collect {|x| x*x } ## Ruby (inject を使うと繰り返しを 1 回にできるが、遅いし不細工) numbers.inject([]) {|arr, x| arr << x*x if 1 <= x && x <= 9; arr }
ただ、式しか書けず、代入とかもできないので、Ruby のブロックとは違って必ずしも万能とはいかない。
## 代入がつかえないために実行効率が悪くなる例 [ x*x for x in numbers if 1000 <= x*x <= 10000 ]
・GC が Reference count 方式である。
C Python での話になるが、Reference count GC であることは絶対に大きな利点だ。よく言われるのが、Mark and Sweep GC のような GC による停止時間がないこと。全体的なスループットでは Mark and Sweep GC のほうが性能がいいらしいが、そんなに違いがあるのかな。そう大きな性能差がないのであれば、停止時間のチューニングに四苦八苦する必要のない Reference count GC のほうが、運用が楽になるから好き。
あとプログラミングが楽になるのもいい。
・バイトコードをファイルに保存できる。
Python では marshal モジュールを使うことで、コンパイル後のバイトコードをファイルに保存できる。そのため eRuby のようなツールの場合、ファイルを Python スクリプトに変換するだけでなく、コンパイルしてバイトコードに変換したあとそれをファイルに保存できる。そうれば 2 回目以降はスクリプトをコンパイルしなくても済むため、読み込みを速くできる。これは CGI のように毎回プロセスを起動する必要がある場合は重要。
Ruby の場合は 1.9 でもバイトコードをファイルに保存できないので、毎回スクリプトをコンパイルする必要がある。そしてこのコンパイルがかなり重いので、CGI は遅くなる。
・ドキュメントが充実している。
Python はリファレンスマニュアルが充実している。PHP や Java と比べても遜色ない。Ruby もあることはあるんだけど、読者の便利さという視点が欠けている。
またそれ以外のドキュメントも充実していて、たとえば古くないチュートリアル(日本語)、拡張案(Python Enhancement Proposals)(日本語)、C API リファレンス(日本語)、など盛りだくさん。
こうしてみると、Ruby はドキュメントが弱いというよりは、開発リソースが言語にばかり偏りすぎているという印象が強い (なんせ、1.9.0 リリース時にも変更点の一覧が公式には用意されていないんだから)。それに比べると、Python はバランスよく開発リソースを割り当てていると思う。