keyhacのPythonスクリプトにも便利なデコレータ

引き続き、keyhacネタ。keyhacはPythonスクリプト(設定ファイル)を記述するのでもちろんデコレータも使えます。デコレータの説明はdecorator.htmlが分かりやすかったので、こちらを見ていただくとして早速コードの紹介。

■ 処理時間計測

上記のサイトで紹介されていたのをちょこっといじったのが、最初に紹介する処理時間計測をするデコレータ。

・デコレータ
from keyhac import *

## 処理時間計測のデコレータ
def profile(func):
    import functools

    @functools.wraps(func)
    def _profile(*args, **kw):
        import time
        timer = time.clock
        t0 = timer()
        ret = func(*args, **kw)
        print '%s: %.3f [ms] elapsed' % (func.__name__, 1000 * (timer() - t0))
        return ret
    return _profile

↑ をconfig.pyの先頭で定義しておく。そして、以下のように測りたい関数の直前に@profileを挿入する。

・使用サンプル
def configure(keymap):

    ## メモ帳を起動
    @profile
    def exe_notepad():
        shellExecute( None, None, "notepad.exe", u"", u"" )

    keymap_global = keymap.defineWindowKeymap()
    keymap_global["W-n"] = exe_notepad

するとこの関数が実行されたとき(=Win+Nが押されたとき)にkeyhacのコンソール画面に以下のような情報が出力されるという仕掛け。

exe_notepad: 352.611 [ms] elapsed

新しく関数を追加するときなどに、こうした方が処理は速くなるかな?ってときにお手軽に時間計測できます。機能は地味だけどとても便利。

■ サブスレッド処理

つぎはサブスレッド化ってやつを簡単にするデコレータ。処理に時間がかかる関数は、サブスレッド処理で実行することが推奨されているようです。

・デコレータ
from keyhac import *

## JobQueue/JobItem でサブスレッド処理にするデコレータ
def job_queue(func):
    import functools

    @functools.wraps(func)
    def _job_queue(*args, **kw):

        num_items = JobQueue.defaultQueue().numItems()
        if num_items:   # 処理待ちアイテムがある場合は、その数を表示
            print u"JobQueue.defaultQueue().numItems() :", num_items

        def __job_queue_1(job_item):
            return func(*args, **kw)

        def __job_queue_2(job_item):
            # print "job_queue : ", func.__name__, args, kw
            pass

        job_item = JobItem(__job_queue_1, __job_queue_2)
        JobQueue.defaultQueue().enqueue(job_item)

    return _job_queue
・使用サンプル

メモ帳起動関数をサブスレッド処理化。(関数定義の前に@job_queueを付ける)

    @job_queue
    def exe_notepad():
        shellExecute( None, None, "notepad.exe", u"", u"" )

もし ↑ をデコレータ使わずに書いたら ↓ になる。

    def exe_notepad():
        def _exe_notepad_1(job_item):
            shellExecute( None, None, "notepad.exe", u"", u"" )

        def _exe_notepad_2(job_item):
            pass

        job_item = JobItem(_exe_notepad_1, _exe_notepad_2)
        JobQueue.defaultQueue().enqueue(job_item)

1つ2つなら良いが、毎回この処理を書くのが面倒になってきて、デコレータを使うようにしたらすごく楽チンになった。

■ キー定義

3つ目はキー定義。このキーが押されたらこの関数の実行するというのを関数定義と一緒に行う。

・デコレータ
from keyhac import *

## キー定義用デコレータ
def def_key(window_keymap, key):
    def _def_key(func):
        window_keymap[key] = func
    return _def_key
・使用サンプル

このデコレータは、@def_key(ウィンドウキーマップ, キー表記文字列) という風に使う。

def configure(keymap):

    keymap_global = keymap.defineWindowKeymap()

    ## メモ帳を起動
    @def_key(keymap_global, "W-n")  # Win + N
    def exe_notepad():
        shellExecute( None, None, "notepad.exe", u"", u"" )

これは以下のコードと同等になる。

def configure(keymap):

    ## メモ帳を起動
    def exe_notepad():
        shellExecute( None, None, "notepad.exe", u"", u"" )

    keymap_global = keymap.defineWindowKeymap()
    keymap_global["W-n"] = exe_notepad  # Win + N

このデコレータではコード記述量は減らないが、関数定義とキーへの紐付けを同時にできるのでコードがシンプルで分かりやすくなる。

■ 関数を返す

4つ目は関数を返す関数にするデコレータ。引数をともなう関数をキー割り当てするときには、関数を返す関数にする必要がある。それが面倒になって作ったのがこのデコレータ。

・デコレータ
from keyhac import *

## 関数を返す関数にするデコレータ
def ret_func(func):
    import functools

    @functools.wraps(func)
    def _ret_func(*args, **kw):
        @functools.wraps(func)
        def __ret_func():
            return func(*args, **kw)
        return __ret_func
    return _ret_func
・使用サンプル

関数定義の前に@ret_funcを挿入する。

def configure(keymap):

    # 引数のパスを起動する
    @ret_func
    def exe_sub(path):
        shellExecute( None, None, path, u"", u"" )

    keymap_global = keymap.defineWindowKeymap()
    keymap_global["W-n"] = exe_sub("notepad.exe")

これは以下のコードと同等になる。

def configure(keymap):

    # 引数のパスを起動する(デコレータ無し版)
    def exe_sub(path):
        def _exe_sub():
            shellExecute( None, None, path, u"", u"" )
        return _exe_sub

    keymap_global = keymap.defineWindowKeymap()
    keymap_global["W-n"] = exe_sub("notepad.exe")

■ さいごに

とりあえずこんなところで。デコレータおもしろい。というかPythonおもしろいです。また何かあったら追加しときます。

追記(2011.08.02)

■ キー定義(別パターン)

3つ目のキー定義の亜種。このサイトの@addtoを使ってwindow_keymapを呼び出し可能にしてしまう。上で書いたやつよりシンプルでいいかもしれない。

・デコレータ
from keyhac import *

# http://wiki.python.org/moin/PythonDecoratorLibrary#Easy_adding_methods_to_a_class_instance
def addto(instance):
    def decorator(f):
        import types
        f = types.MethodType(f, instance, instance.__class__)
        setattr(instance, f.func_name, f)
        return f
    return decorator

def configure(keymap):

    def define_window_keymap(*args, **kw):
        window_keymap = keymap.defineWindowKeymap(*args, **kw)
        print args, kw

        @addto(window_keymap)
        def __call__(self, key):
            def def_key(func):
                self[key] = func
            return def_key

        return window_keymap
・使用サンプル
    # keymap.defineWindowKeymap の代わりに define_window_keymap を使う
    keymap_global = define_window_keymap()

    # 通常どおりのキー設定も可能
    keymap_global["S-C-w"] = "LWin"

    # デコレータにもなる
    @keymap_global("W-n")
    def exe_notepad():
        shellExecute( None, None, "notepad.exe", u"", u"" )

■ 参考リンク