ASPN : Python Cookbook : Callback Pattern

今日もデコレータ。コールバック関数を書くための簡単フレームワーク的なレシピで、昨日のデコレータの使い方と全然違う。メタクラスを使えばもっとエレガントに書けるかも、とのこと。

クラスのstaticmethodをデコレータとして、デコレートされた関数オブジェクト自体にあらかじめイベントを表わす属性を付加しておくというやり方をしている。
CallbackBase継承クラスのインスタンス生成時に、キーイベントとコールバックされるべき関数がマップされる。

具体的にはdirで属性リストを取得して、getattrでその属性の値を取得。でこのときデコレータで付加したイベント属性が存在するかをチェックする。あればコールバックすべき関数なので、イベントとその関数とをディクショナリに入れておく。イベントはリストにしておく。
dispatchメソッドにイベントとパラメータを渡してやれば、コールバック関数が実行される。

dispatchメソッドの中にはlambdaが二回出てくる。マップから関数リストを取り出して一つずつ実行するので一つ。この処理全体を関数として返すので一つ。全体を関数として返すのにはどういうメリットがあるんだろうかよく分からない。

コールバック関数はインスタンスメソッドになっている。callback(event)やcallbacklist(event)が呼ばれるのはインスタンス生成より早く、クラス定義読み込み時のようだけど、コールバック実行時にはselfがちゃんと決まっている。
してみると、self.dispatch(event)(param)という呼び出しで、dispatchメソッド内の*argsにはselfも入ってくるということだろう。不思議な動きに思える。

ところで、このままだとディスパッチするクラスとコールバック関数を持つクラスが同じクラスじゃないといけないので、あんまりありがたみがないような気がする。コールバック関数は好きな場所に書けたほうが便利なので、以下のようにしてみる。

CallbackBaseの外側にリストを置いて、

callback_funcs = []

CallbackBaseのinitを次のように書き換える。

class CallbackBase:
    def __init__(self):
        self.__callbackMap = {}
        for k in callback_funcs:
            # 中身は同じ

あとはcallbackとcallbacklist内の「return g」の前に以下を挿入。

callback_funcs.append(g)

で、下のコードを実行してみる。

class Dispatcher(CallbackBase):
    EVENT1 = 1
    EVENT2 = 2
    def run(self, event, param=None):
        self.dispatch(event)(param)

class CallbackClass():
    @Dispatcher.callback(Dispatcher.EVENT1)
    def handler1(param=None):
        print "handler1 with param: %s" % str(param)
        return None

    @Dispatcher.callbacklist([Dispatcher.EVENT1, Dispatcher.EVENT2])
    def handler2(param=None):
        print "handler2 with param: %s" % str(param)
        return None


dispatcher = Dispatcher()
dispatcher.run(Dispatcher.EVENT1, 'mandarina')
dispatcher.run(Dispatcher.EVENT2, 'naranja')

結果は以下の通りコールバック関数が実行されている。

handler1 with param: mandarina
handler2 with param: mandarina
handler2 with param: naranja

見ての通りhandlerとhandler2がインスタンスメソッドじゃなくなってしまったのがどうかという気がするが、これはこれでデコレータの特長を生かしていると言えなくもないかな。(追記)しかしこれだとクラスを使う意味が全然ないなぁ・・・。
もっといいやり方があるだろうか。