kなんとかの日記 このページをアンテナに追加

2008-04-17

Python の好きなところ

| 08:49 |  Python の好きなところを含むブックマーク

Python の好きなところ。主に Ruby との対比。

微妙なものも混ざっているけど、ご容赦ください。


キーワード引数を装備している。

これは Ruby と比べて大きなアドバンテージRuby の、Hash を使った疑似キーワード引数も悪くはないんだけど、使用できるキーワード引数に明示されないので readability が低い、キーワードを間違ってもエラーにならない、という欠点がある。やはり Ruby でも本物のキーワード引数が欲しい。

しかしキーワード引数を持っている言語って、そう多くはないんだよね。Ruby だけでなく、PerlPHPJS も持っていない。もう 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 によく似ている

Pythonclass base だけど、JavaScript のような prototye base にとてもよく似ている。PythonJavaScript もメソッドの実体は関数だけど、そういう言語では 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リファレンスマニュアルが充実している。PHPJava と比べても遜色ない。Ruby もあることはあるんだけど、読者の便利さという視点が欠けている。

またそれ以外のドキュメントも充実していて、たとえば古くないチュートリアル(日本語)、拡張案(Python Enhancement Proposals)(日本語)、C API リファレンス(日本語)、など盛りだくさん。

こうしてみると、Ruby はドキュメントが弱いというよりは、開発リソースが言語にばかり偏りすぎているという印象が強い (なんせ、1.9.0 リリース時にも変更点の一覧が公式には用意されていないんだから)。それに比べると、Python はバランスよく開発リソースを割り当てていると思う。