デコレータ
これまでに関数をネストしたり、関数を引数として受け取れることは理解したはずです。
これをうまく使うと関数を修飾するような定義を実装することができます。
code: 0608_decorator.py
def my_decorator(func):
def wrapper():
print("before the function is called.")
func()
print("after the function is called.")
return wrapper
def say_hello():
print("Hello!")
say_hello = my_decorator(say_hello)
say_hello()
これを実行すると次のようになります。
code: bash
$ python 0608_decorator.py
before the function is called.
Hello!
after the function is called.
関数say_hello()はmy_dectorator()によって修飾されたものに置き換えられています。
こうした、関数を修飾することをデコレータ(decorator) と言います。
ただし、この方法では関数の再定義となり、プログラムを理解するのが少し難しくなってしまいます。
こうした問題を対処するためにアットマーク(@) に続けて修飾する関数名を表記する方法が提案されました。(PEP318) トリビア: こうした構文を彩る表現を Syntactical sugar といいます。
code: 0609_decorator_with_atsign.py
def my_decorator(func):
def wrapper():
print("before the function is called.")
func()
print("after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
モジュールにしてデコレータを利用しやすくする
先の例のmy_decorator() をモジュールにしてみましょう。
code: my_decorators.py
def my_decorator(func):
def wrapper():
print("before the function is called.")
func()
print("after the function is called.")
return wrapper
これを次のように記述すると、他のスクリプトからも簡単に再利用できるようになります。
code: 0610_decorator_with_module.py
from my_decorators import my_decorator
@my_decorator
def say_hello():
print("Hello!")
say_hello()
引数を持つデコレータ関数
上記の関数say_hello()に引数を与えたいときもありますよね。
code: 0611_decorator_with_arguments.py
from my_decorators import my_decorator
@my_decorator
def say_hello(name):
print(f"Hello {name}!")
say_hello('Python')
このまま実行すると次のようにエラーになってしまいます。
code: bash
$ python 0611_decorator_with_arguments.py
Traceback (most recent call last):
File "sample_decorator4.py", line 7, in <module>
say_hello('Python')
TypeError: wrapper() takes 0 positional arguments but 1 was given
モジュールmy_decoratorsのmy_decorator()で関数 warpper()が、
引数を取らない定義となっているからですね。
そこで、次のようにモジュールを修正してみましょう。
code: my_decorators2.py
def my_decorator(func):
def wrapper(*args, **kwargs):
print("before the function is called.")
func(*args, **kwargs)
print("after the function is called.")
return wrapper
0611_decorator_with_arguments.pyとほとんど同じなのですが、モジュール名が変わっているので次のような修正が必要になります。
code: 0612_decorator_with_arguments.py
from my_decorators2 import my_decorator
@my_decorator
def say_hello(name):
print(f"Hello {name}!")
say_hello('Python')
これでうまくいくようになりました。
code: bash
$ python 0612_decorator_with_arguments.py
before the function is called.
Hello Python!
after the function is called.
定義の実態を知りたい
デコレータを使うと便利になのですが、本当の定義はどれなのかが見えなくなってしまい、デバッグがしずらくなることもあります。
code: 0613_decorator_with_attributes.py
from my_decorators2 import my_decorator
@my_decorator
def say_hello(name):
print(f"Hello {name}!")
print(say_hello)
print(say_hello.__name__)
これを実行すると次のようにsay_hello.__name__ が wrapper に置き換えられてしまっています。
code: bash
$ python 0613_decorator_with_attributes.py
<function my_decorator.<locals>.wrapper at 0x10fd1a400>
wrapper
こうしたことに対応すためには次のようにデコレータ関数を定義します。
code: my_decorators3.py
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("before the function is called.")
func(*args, **kwargs)
print("after the function is called.")
return wrapper
例として上げるためにモジュール名を変えていますので、インポートする側のファイルも変わってしまいます (^^;
code: 0614_decorator_with_attributes.py
from my_decorators3 import my_decorator
@my_decorator
def say_hello(name):
print(f"Hello {name}!")
print(say_hello)
print(say_hello.__name__)
code: bash
$ python 0614_decorator_with_attributes.py
<function say_hello at 0x104b62400>
say_hello