導入#
関数の実行前後に何かしらの処理を行いたい場合、以下のように書くことができます:
def foo():
print('foo()!!!')
def bar():
print('Before')
foo()
print('After')
bar()
デコレータの実装#
しかし、これは少し不自然に見えます。以下のように書くこともできます:
def foo():
print('foo()!!!')
def bar(func):
def inner():
print(f'Before {func.__name__}')
func()
print(f'After {func.__name__}')
return inner
bar(foo)()
Python では、関数が第一級オブジェクトであるため、関数を引数として渡すことができます。
これにより、シンプルなデコレータが実装されました。しかし、呼び出し時に少し手間がかかることに気付くかもしれません。Python では、次のようなシンタックスシュガーも提供されています:
def bar(func):
def inner():
print(f'In Bar Before {func.__name__}')
func()
print(f'In Bar After {func.__name__}')
return inner
@bar
def foo():
print('foo()!!!')
foo()
さらに、Python のデコレータは複数持つことができ、一種のラッピングのようなものです:
def bar(func):
def inner():
print(f'In Bar Before {func.__name__}')
func()
print(f'In Bar After {func.__name__}')
return inner
def baz(func):
def inner():
print(f'In Baz Before {func.__name__}')
func()
print(f'In Baz After {func.__name__}')
return inner
@bar
@baz
def foo():
print('foo()!!!')
foo()
上記のコードの出力は次のとおりです:
In Bar Before inner
In Baz Before foo
foo()!!!
In Baz After foo
In Bar After inner
一つの応用例#
次のような関数があるとします:
import time
def slow_method():
time.sleep(2)
print('Done!')
この関数の実行時間を知りたい場合、デコレータを使用して実装することができます:
import time
def timeit(func):
def inner():
s = time.time()
func()
e = time.time()
print(f'{func.__name__} Finished in {e - s}s.')
return inner
@timeit
def slow_method():
time.sleep(2)
print('Done!')
slow_method()
実行結果:
Done!
slow_method Finished in 2.0159008502960205s.
これにより、要件が満たされました。
デコレートされた関数に引数がある場合も同様に動作します:
import time
def timeit(func):
def inner(*args):
s = time.time()
func(*args)
e = time.time()
print(f'{func.__name__} Finished in {e - s}s.')
return inner
@timeit
def slow_method(a, b):
time.sleep(2)
print(f'{a} + {b} = {a+b}')
print('Done!')
slow_method(1, 2)
実行結果:
1 + 2 = 3
Done!
slow_method Finished in 2.0069477558135986s.
メモ化再帰を実現するためのデコレータの巧妙な使用#
まず、素朴なフィボナッチ関数があるとします:
def fib(n):
if n <= 1: return 1
return fib(n - 1) + fib(n - 2)
明らかに、この関数は非常に効率が悪いです。メモ化再帰を使用してこの関数を最適化することができます。
cache = {}
def fib(n):
if n in cache: return cache[n]
if n <= 1: return 1
cache[n] = fib(n - 1) + fib(n - 2)
return cache[n]
print(fib(333))
これにより、fib 関数がはるかに高速になります。ただし、実装が少し複雑になるかもしれません。デコレータを使用してコードを簡素化することができます:
class MyCache(object):
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
if args not in self.cache:
self.cache[args] = self.func(*args)
return self.cache[args]
@MyCache
def fib(n):
if n <= 1: return 1
return fib(n - 1) + fib(n - 2)
print(fib(333))
これにより、fib 関数が非常にエレガントに実装されます。