ロバストPythonでも出てきたが、自分でデコレータを作成してみた。引数ありなしで少し実装が変わるので注意したい。
デコレータ
デコレータは関数の前後に実行したい振る舞いを定義することができる。
コンテキストマネージャとの違いは?
コンテキストマネージャとデコレータは共に、処理の前後で何かしらの処理をしたいときに使うイメージがある。 私の理解だと下記の違いがあると考える。
- 関数の前後で何かしらの処理をしたい場合に使用
- ある特定の処理のブロックの前後で何かしらの処理をしたい場合に使用
コンテキストマネージャの場合、with文と使うことでファイルオープンしたオブジェクトをブロック内の処理に渡すことができるので、個人的にコンテキストマネージャの方が使いやすいという印象がある。
自前のデコレータを作るには
引数なしのデコレータと引数ありのデコレータでは実装が少し違うので、実際に作ってみる。
引数なしのデコレータ
下記の例の場合、calling
デコレータを用意した。
callingデコレータとなる関数のパラメータにCallableを指定。これがデコレータを付与した関数が設定される。
_wrapper
関数をcalling
関数内で宣言し、_wrapper
関数を返却する。
_wrapper
関数は、パラメータとして、*args
と**kwargs
を設定する。
*args
と**kwargs
にデコレータを設定した関数のパラメータが設定される。
下記の例の場合、tanaka
と 20
が *args
に設定されている。
""" デコレータにする関数を宣言。パラメータはCallable """ def calling(func: Callable) -> Callable: """ _wrapper関数を宣言。パラメータは*argsと**kwargs デコレータを付与した関数のパラメータが設定される。 """ def _wrapper(*args, **kwargs): print("calling decorator start ------->") print("args = " + str(args)) # デコレータを付与した関数を実行 func(*args, **kwargs) print("---------> calling decorator end") return _wrapper @calling def execute(name: str, age: int): print("execute start") if __name__ == '__main__': execute("tanaka", 20)
実行すると下記のように、デコレータにした関数の前後のメッセージがデコレータを付与した関数の前後で出力されているのがわかる。
calling decorator start -------> args = ('tanaka', 20) execute start ---------> calling decorator end
引数ありのデコレータ
引数ありのデコレータの場合、デコレータにする関数の中で_wrapper
関数を呼ぶ内部関数を定義する必要がある。
引数なしのデコレータとの違いは、 デコレータとなる関数から返す関数が_wrapper
関数なのか、_wrapper
関数をもつ内部関数を返すかの違い。
下記の場合、calling_tax
デコレータを用意。パラメータがデコレータで指定するパラメータで戻り値はCallable。
calling_tax
関数で内部関数を定義する。
この関数のパラメータはCallable
で、デコレータを付与した関数が設定される。
内部関数の_calling_tax
関数で_wrapper
関数を用意する。ここは引数なしのデコレータと一緒。
""" デコレータにする関数を宣言 パラメータはそのままデコレータのパラメータになる """ def calling_tax(name: str) -> Callable: """ 内部関数を宣言。パラメータはCallable このCallableは、デコレータを設定した関数が設定される """ def _calling_tax(func: Callable): """ _wrapper関数内で処理を実行 *args、**kwargsがデコレータを付与した関数のパラメータの値が設定される """ @functools.wraps(func) def _wrapper(*args, **kwargs): print("calling_tax decorator start ------>") print("name = " + name) print("args = " + str(args)) func(*args, **kwargs) print("-------> calling_tax decorator end") return _wrapper return _calling_tax @calling_tax("watanabe") def execute_tax(address: str, tel: int): print("start execute_tax") print("address = " + str(address)) print("tel = " + str(tel)) if __name__ == '__main__': execute_tax("Kanagawa", 11111)
実行結果は下記のように、デコレータのパラメータも渡されており、デコレータを付与した関数の前後で設定したメッセージが出力されていることがわかる。
calling_tax decorator start ------> name = watanabe args = ('Kanagawa', 11111) start execute_tax address = Kanagawa tel = 11111 -------> calling_tax decorator end
まとめ
自分でデコレータを作成する方法を理解できた。
引数あり/なしで実装の仕方が少し違うので注意したい。
コンテキストマネージャとの使い分けも今後、考えながら自分でデコレータを用意して実装をシンプルに実現できるようにしていきたい。