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')

おすすめ