モジュール

モジュールを少し勉強した。

module Trig
  PI = 3.141592654
  def Trig.sin(x)
    # ..
  end
end

requre 'trig'

y = Trig.sin(Trig::PI/4)

reqireで文字列で渡すのはまあ良いとして、定数アクセスの"::"と、メソッドアクセスの"."を区別しているのは気に入らない。reqireした際に変数は取り込まれないのも覚えるのが大変そう。メソッド定義で"Trig."を付けるのが面倒など色々不満が出た。もう少し使い込んでみないとそうすべき理由が見えない。


minxinの説明で、include Enumerableして、eachメソッドを定義すれば、map、include?、find_all?、injectなどが普通に使えるようになるのは良さそうに感じた。


あと、Rubyはビルトインクラスの挙動をプログラム全体で変更できるが、Pythonは基本的にビルトインの挙動は継承する以外は変更不能なので安全だが、Rubyは思い切ったことができる。あと以下の例を見てコードを短く書けるというのを感じた。

class VowelFinder
  include Enumerable

  def initialize(string)
    @string = string
  end
  def each
    @string.scan(/[aeiou]/) do |vowel|
      yield vowel
    end
  end
end
vf = VowelFinder.new("the quick brown fox jumped")
vf.inject {|v,n| v+n }  #=> "euiooue"

やはりjijixiさんの言うとおり、ちょっとしたコードを書くならRuby便利かな。Pythonでもライブラリ作りこんでおけば同等以上なパワーは得られると思うが、結局標準ライブラリの力は大きいかも。もちろんこの例の程度ならPythonでも十分ではある。とりあえずコレクションクラスの使い方を押さえたいなあ。


しかし、endが不要な分Pythonの方が縦に短くなる可能性があるが、Rubyは式と文があまり区別なさそうなので、ショートコードでそこは大きいのかも。というか、Rubyではある意味すべて式?

関数の実行時間計測

関数の実行時間を計測する場合は、セットアップ文に、"form __main__ import 関数名"を指定します。そうしないと、timeitモジュールが関数にアクセスできないようです。

from timeit import Timer

def range_test():
    n = 0
    for i in range(1000): n += 1

t1 = Timer("range_test()", "from __main__ import range_test")

print t1.timeit(10000)

うおっ!関数をそのままtimeitモジュールで計測できるの知らなかった。

でも誰も触れていないということは結構知られていない気がする。timeitモジュールのここら辺の仕組みは難しいなあ。後でEffective Pythonに追記しておこう…。


追記:

timeit.pyのソースを読んで理解できた。要はTimer(stmt="pass", setup="pass", timer=default_timer)で、単にsetupを1回とstmtを指定した回数実行して時間を計測しているだけだった。つまり、stmtもしくは、setupに関数の定義を渡してあげないと関数が呼び出せないということだった。

def foo(func):
    import timeit
    t1 = timeit.Timer('Timer.__dict__["r"] = %s()' % func.__name__, 'from %s import %s' % (__name__, func.__name__))
    print t1.timeit(1), 'sec elapsed'
    return timeit.Timer.__dict__['r']

少し汎用性のあるように関数を作成してみた。上記のfoo()に関数オブジェクトを引数で渡すと、渡された関数を無引数で呼び出した際の実行時間が表示される。おまけとして、戻り値も返すようにした。


stmtの実行はTimer#inner()で行っている。inner()の定義は、stmtとsetupを埋め込んだinner()関数を文字列で定義して、それをビルトインのcompile()関数でコード化してexecで実行した結果(つまりinner()関数を定義した結果)をself.innerに代入してインスタンスメソッドとしている。ここは説明よりもコードを直接見た方が分かりやすい。


戻り値は、stmtの実行結果はinner()関数内で捨てている。Pythonでは文は戻り値を戻さないのである意味当たり前。そこで、stmt自体に戻り値を"どこかに"保存しておく処理を書くしかない。そこで、Timerクラスの__dic__に保存して後で取り出している。


しかし、時間計測は、結局win32であれば、time.clock()の戻り値の差、それ以外であれば、time.time()の戻り値の差を返しているだけなので、関数呼び出しの戻り値を返したいなら自分でその処理を書いた方が簡単である。よって、エラー処理を考慮しないのであれば、どう書く?orgに書いた自分の方法でデコレータを実現する方法で問題ない。time.timeよりもtime.clockを使った方が良かったという程度。