なんとなく、PythonでAOPっぽいことをやってみたくなりまして。実現方法を考えてみましたが、1番簡単なのが関数デコレータを使うことかなぁと。
関数デコレータは、文字通り関数を拡張するものです。まあ、とりあえず書いてみましょう。AOP適用の例としては、簡単にbefore/afterログで。
aop_decorator.py
#/usr/local/python # -*- coding: utf8 -*- def trace(func): def _trace(*args, **keyword): print 'Called [%s], args[%s, %s] before' % (func.__name__, args[1:], keyword) result = func(*args, **keyword) print 'Called [%s], args[%s, %s] after' % (func.__name__, args[1:], keyword) return result return _trace
ごく普通の関数ですが、これを利用して既存の関数を拡張することができます。なお、適用する関数はどんな引数を受け取るかわからないので、可変長引数*args, **keywordを利用しており、関数を返す関数にもなっています。
動作としては、この関数デコレータを適用した関数が呼び出された時に、その関数名と引数を出力するようになります。
適用例は、以下のコードです。
decorated.py
#!/usr/bin/python # -*- coding: utf-8 -*- from aop_decorator import trace class Greeting(object): def __init__(self, name): self.name = name @trace def greet_trace(self, target): print "Hello %s to %s" % (self.name, target) def greet(self): print "Hello %s!" % self.name if __name__ == "__main__": g = Greeting("Python") g.greet_trace("AOP") g.greet()
Greetingクラスのgreet_traceメソッドに、@traceで関数デコレータを付与しています。これで、関数デコレータを適用したことになります。
実行すると、こうなります。
$ python decorated.py Called [greet_trace], args[('AOP',), {}] before Hello Python to AOP Called [greet_trace], args[('AOP',), {}] after Hello Python!
一応、ちゃんと動作しています。当然ですが、関数デコレータを付与していないメソッドには、何の効果もありません。
さっきの関数デコレータをもっかい見てみましょう。
def trace(func): def _trace(*args, **keyword): print 'Called [%s], args[%s, %s] before' % (func.__name__, args[1:], keyword) result = func(*args, **keyword) print 'Called [%s], args[%s, %s] after' % (func.__name__, args[1:], keyword) return result return _trace
実際に動く時には、trace関数の引数に本来の呼び出し対象の関数/メソッドが渡ってきます。これを自由変数として取り込んだ内部関数_traceを返却すると、外側からは本来呼び出したい関数を呼び出すと実際には_trace関数が呼び出されます。その時に、渡ってきた引数argsおよびkeywordを引数にfuncを呼び出すという仕掛けです。
よって、実体の関数を呼び出しているのは、以下の部分です。
result = func(*args, **keyword)
なお、関数デコレータをクラスとして宣言する試みもやってみたのですが、こちらはうまくいかず…。
class trace_class(object): def __init__(self, func): self.func = func def __call__(self, *args, **keyword): print 'Called [%s], args[%s, %s] before' % (self.func.__name__, args, keyword) self.func(*args, **keyword) # これでは動かない… print 'Called [%s], args[%s, %s] after' % (self.func.__name__, args, keyword)
こんな感じで試してみたんですけど、残念ながら動作しません。対象のメソッドを呼び出す際に、相手のオブジェクト参照selfが足りず、エラーになってしまいます。これをどうやって取得したらいいのかが、ちょっとわかりませんでした…。