Pythonデコレータの使いどころ
Pythonのデコレータ機能の使いどころについてまとめてみました
デコレータの概要
Pythonのデコレータは@staticmethod
など既存のものを使用することが多いと思いますが、ユーザで独自のデコレータを定義することも可能です。デコレータとはなんぞや?というと、おしゃれな簡単にいうとラッパ関数の指定方法です。例えば、関数を二度呼び出すcall_twice()
ラッパ関数を使用した関数呼び出しは以下のよう記載できます
def call_twice(func): def wrapper(): func() func() return wrapper @call_twice def say_hello(): print("Hello") say_hello()
% python decorator_example.py Hello Hello
上記コードはデコレータを用いずに書くと以下のようになります
def call_twice(func): def wrapper(): func() func() return wrapper def say_hello_(): print("Hello") say_hello = call_twice(say_hello_) say_hello()
デコレータを以下のように書くと、引数や戻り値の受け渡しも可能です
def call_twice(func): def wrapper(*args, **kwargs): func(*args, **kwargs) return func(*args, **kwargs) return wrapper
上記の例ではいまいち使いどころが分からないため、具体的な使用例についてまとめめてみました
使用例
関数実行時間の計測
関数開始と終了の時間を記録することで実行時間を表示します
import functools import sys import time def exe_time(func): @functools.wraps(func) def wrapper(*args, **kwargs): start_time = time.perf_counter() ret = func(*args, **kwargs) end_time = time.perf_counter() print(f"[{end_time}] {func.__name__} execution time is {end_time - start_time}s") return ret return wrapper @exe_time def say_hello(): time.sleep(0.5) print("Hello") def main(): say_hello() if __name__ == "__main__": sys.exit(main())
デバッグ用に引数と戻り値を表示
下記の例ではデコレータで修飾した関数について呼び出し時の引数と戻り値を表示します
import functools import logging import sys import time def debug_func(func): @functools.wraps(func) def wrapper(*args, **kwargs): logging.debug(f"{func.__name__} (args={args}, kwargs={kwargs})") ret = func(*args, **kwargs) logging.debug(f"{func.__name__} return {ret}") return ret return wrapper @debug_func def is_negative(v): return v < 0 def main(): logging.basicConfig(level=logging.DEBUG) is_negative(0.1) if __name__ == "__main__": sys.exit(main())
ソケットやログインなどの初期化処理確認
デコレータでログイン状態やソケットの接続状態を確認すれば、関数毎にチェックコードを記載する必要がなくなります。クラスのメンバ関数に対するデコレータでは、wrapper
の第一引数をself
としてクラスメンバへのアクセスが可能です
import socket def connection_required(func): @functools.wraps(func) def wrapper(self, *args, **kwargs): if not self.is_connected(): self.connect() return func(self, *args, **kwargs) return wrapper class NetworkClient: def __init__(self): self.socket = None def is_connected(self): return self.socket is not None def connect(self): self.socket = socket.socket() @connection_required def send_data(self, data): print("send_data...") # ... def main(): client = NetworkClient() client.send_data(b'hello')
最近のコメント