Pythonのデコレーターの使い方
数カ月前にPythonのデコレーターを勉強した時はよく分かんなかったんですが、今日勉強してみたらなんか分かった気がするのでめも。間違ってたらごめんなさい。
以下のサイトを参考にしてます。
・Python decorator
日本語で分かりやすいです。
・PEP 318 -- Decorators for Functions and Methods | Python.org
例はここのを使用してます。
デコレーターって何?
class x(): def foo(cls): pass foo = classmethod(foo)
と、する必要がありました*2。これじゃ面倒くさいよねってことで考えられたのがデコレーターです。デコレーターを使うと上のクラスは次のように書き換えることができます。
class x(): @classmethod def foo(cls): pass
このように、ある関数(ここではfoo)を変形させるのに使用するのがデコレーターです。ここでは組み込み関数のclassmethod()を使いましたが、自分で定義した関数を使うこともできます。
デコレーターの構文
デコレーターは次のように使います。
@dec1 def func(arg1,arg2,...): pass
これは次の文と等価です。
def func(arg1,arg2,...): pass func = dec1(func)
例1: 実行時に登録される関数を定義 (この例では関数自体に変更はされません)
def onexit(f): import atexit atexit.register(f) return f @onexit def func(): ...
(注:atexit.register()で登録した関数はインタプリタが終了するときに自動的に実行されます。)
あくまでもこれは例なので実用的ではありません。func = onexit(func)を実行するので、デコレーターは必ず戻り値を返す必要があります。
デコレーターは複数適用させることができます。
@dec2 @dec1 def func(arg1,arg2,...): pass
これは、 func = dec2(dec1(func)) としているのと一緒です。合成関数 (g o f)(x) が g(f(x)) になるような感じです。
また、デコレーター自体に引数を与えることができます。
@dec(argA,argB,...) def func(arg1,arg2,...): pass
これは次の文と等価です。
func = dec(argA,argB,...)(func)
例2: 関数に属性を追加
def attrs(**kwds): def decorate(f): for k in kwds: setattr(f,k,kwds[k]) return f return decorate @attrs(versionadded="2.2", author="Guido van Rossum") def mymethod(f): ...
関数が入れ子になっていてややこしいですが、次のように考えるといいんだと思います。
1. @attrs(versionadded="2.2",author="Guido van Rossum") なので、 attrs(versionadded="2.2",author="Guido van Rossum")が実行される。この戻り値として、kwdsが与えた引数に置きかえられたdecorate関数が返る。
2. mymethod = (attrs(versionadd...)の戻り値の関数)(mymethod)が実行される。つまり、結果としてはmymethod = decorate(mymethod)が実行される。
3. decorate()内でmymehodにsetattr()によって属性が追加される。
実際の内容はdis/inspect モジュールを使った Python のハッキングが参考になると思います。
例3: デコレータによって引数と戻り値の型を強制
def accepts(*types): def check_accepts(f): def new_f(*args, **kwds): for (a, t) in zip(args, types): assert isinstance(a, t), \ "arg %r does not match %s" % (a,t) return f(*args, **kwds) new_f.func_name = f.func_name return new_f return check_accepts def returns(rtype): def check_returns(f): def new_f(*args, **kwds): result = f(*args, **kwds) assert isinstance(result, rtype), \ "return value %r does not match %s" % (result,rtype) return result new_f.func_name = f.func_name return new_f return check_returns @accepts(int, int) @returns(int) def func(arg1, arg2): return arg1 * arg2 print func(1,2) print func(1.0,2) # 引数エラー
この例では、check_returns(f)内でさらに関数を定義して、それを返すことで関数f()を根本的に書き換えています。まずはじめに@returnsによってfuncが書き換えられ、その書き換えられた関数に対して@acceptが適用されます。実際にfunc()を実行するときにはまず引数がチェックされ、その後元のfunc()を呼びだし、最後に戻り値をチェックしています。
これで一通りデコレーターについての説明は終わりです。まぁデコレーターを使うのは簡単ですが、作るのは慣れないとなかなか大変だと思います。PythonDecoratorLibrary - Python Wikiというページにデコレーターのサンプルがたくさん用意されてるので、これを参考にするといいと思います。
上のサイトからいくつか紹介します。
例4: メモ化 (3.Memoize)
class memoized(object): def __init__(self,func): self.func = func self.cache = {} def __call__(self,*args): try: return self.cache[args] except KeyError: # キャッシュされていない場合 value = self.func(*args) self.cache[args] = value return value @memoized def fibonacci(n): if n in (0,1): return n return fibonacci(n-1) + fibonacci(n-2)
ここではデコレータとしてクラスを使っています。結果としてfibonacciはmemoizedのインスタンスになります(fibonacci = memoized(fibonacci))。
同じことを関数でやることももちろんできます。
def memoize(func): cache = {} def memoized_function(*args): try: return self.cache[args] except KeyError: value = func(*args) cache[args] = value return value return memoized_function @memoize def fibonacci(n): if n in (0,1): return n return fibonacci(n-1) + fibonacci(n-2)
まぁ自分が分かりやすい方を使えばいいと思います。
例5: 関数が呼ばれた回数をカウントする (8.Counting function calls
class countcalls(object) __instances = {} def __init__(self,f): self.__f = f self.__numcalls = 0 countcalls.__instances[f] = self def __call__(self,*args,**kwargs): self.__numcalls += 1 return self.__f(*args,**kwargs) @staticmethod def count(f): return countcalls.__instances[f].__numcalls @staticmethod def counts(): return dict([(f,countcalls.conut(f)) for f in countcalls.__instances])
こんな感じで使えます。
@countcalls def f(): print "f" @countcalls def g(): print "g" f() f() f() g() g() print countcalls.count("f") # 3 print countcalls.counts() # {'g': 2, 'f': 3}
他にも沢山ありますがとりあえず今日はここまでで^^;。