概要
Pythonを使っているとdef
の上部に@がくっついていることがありますよね。
@main.result_callback()
def run(a, b):
....
これ、関数デコレータ(以下、デコレータ)と呼ばれるものらしいのですが、調べてみてもよくわからないし、使い道もサッパリだったので無視してました。
だがしかし。
大量にデコレータがついているソースコードに遭遇してしまい、どうしても理解しないといけない状況になりました。
仕方なく重い腰をあげて勉強してみます。
デコレータの基本の「き」
デコレータの最低限の動きを確認するために次のコードを実行してみます。
def decorator(f):
def inner():
print('before')
retval = f()
print('after')
return retval
return inner
@decorator
def example():
print('inside')
example()
実行結果
before
inside
after
デコレータを少しでも調べたことがある人は、デコレータdecorator
を付与した状態でexample()を実行することはdecorator(example)を実行することと同じであるという説明をみたことがあると思います。そのため、decoratorの引数f
をexample関数として読み替えれば最低限の動きは理解できるかと思います。
print('before')
retval = example()
print('after')
ちなみにexample関数はretval = example()
と代入しているだけですが、これで関数が実行されます。
例えば下記のように書いてみるとhoge関数は実行されてhelloが表示されます。
def hoge():
print('hello')
f = hoge
x = f()
実行結果
hello
デコレータを使うことで、ある関数(example関数)の処理を変更せずにその前後に別の処理を入れることができることがわかりました。
これだけじゃ使い物にならない
実行関数に引数を伴う場合
example()
関数に引数を伴う場合はエラーになってしまいます。
def decorator(f):
def inner():
print('before')
retval = f()
print('after')
return retval
return inner
@decorator
def example(x):
print(x)
print('inside')
example('hello')
TypeError: inner() takes 0 positional arguments but 1 was given
この場合は次のようにdecorator()
, inner()
に可変長引数*args, **kwargs
を指定すればOKです。
def decorator(f, *args, **kwargs):
def inner(*args, **kwargs):
print('before')
retval = f(*args, **kwargs)
print('after')
return retval
return inner
@decorator
def example(x):
print(x)
print('inside')
example('hello')
実行結果
before
hello
inside
after
デコレータに引数を指定したい場合
デコレータdecorator()
に引数を指定したい場合は次のように書くことで実現できます。
def decorator(start, end):
def _time_args(f):
def inner(*args, **kwargs):
print(start)
print('before')
retval = f(*args, **kwargs)
print('after')
print(end)
return retval
return inner
return _time_args
@decorator('開始', '終了')
def example(x):
print(x)
print('inside')
example('hello')
実行結果
開始
before
hello
inside
after
終了
うーん、どんどん入れ子になって混乱してきた。
さいごに
デコレータの理解を深めるにはPythonの理解をもっと深めていく必要があるなあーと感じました。
参考文献