Pythonで柔軟なデコレータの実装方法

一般的に、引数のないデコレータは括弧なしで使用し、引数のあるデコレータは括弧付きで使用します。例えば:

  • 引数なしデコレータ
def log(func):  # デコレータは関数を引数として受け取り、関数を返す
    @functools.wraps(func)
    def wrapper(*args, **kwargs):   # 元の関数と同じ機能をサポート
        print(f'関数呼び出し: {func.__name__} 引数: {args} {kwargs}')
        return func(*args, **kwargs)   # 内部で元の関数を呼び出す

    return wrapper  # 元の関数と同じ機能を持つ関数を返す

@log   # 使用時は括弧不要
def add(a, b):
    return a + b
  • 引数ありデコレータ(デコレータを返す関数)
def log(show_result=True):

    def _log(func):  # デコレータは関数を引数として受け取り、関数を返す
        @functools.wraps(func)
        def wrapper(*args, **kwargs):  # 元の関数と同じ機能をサポート
            print(f'関数呼び出し: {func.__name__} 引数: {args} {kwargs}')
            start_time = time.time()
            result = func(*args, **kwargs)  # 内部で元の関数を呼び出す
            if show_result:
                print(f'結果: {result} 処理時間: {time.time()-start_time}')
            return result

        return wrapper  # 置き換えられた新しい関数を返す

    return _log

@log()  # デフォルトパラメータを使用する場合でも括弧が必要(デコレータを取得するため)
def add(a, b):
    return a + b

注意: 引数付きデコレータはパラメータによるカスタマイズをサポートしますが、使用時は括弧が必要です。ユーザーは括弧を忘れたり、必要があるかどうかを判断しにくくなることがあります。

pytest.fixtureのように、引数なしでも引数付きでも使用できるデコレータはどうでしょうか。例:

import pytest

@pytest.fixture
def a(): ...

@pytest.fixture()
def b(): ...

@pytest.fixture(scope='module')
def c(): ...

これを実現するには、デコレータの外側の関数がデコレータ自体(元の関数を受け取り、同機能の関数を返す)として機能し、パラメータ呼び出し後にデコレータを返す必要があります。実装は以下の通りです:

def logger(func=None, show_output=False):  # 最初のパラメータは関数

    def _logger(func):  # デコレータは関数を引数として受け取り、関数を返す
        @functools.wraps(func)
        def wrapper(*args, **kwargs):  # 元の関数と同じ機能をサポート
            print(f'関数実行: {func.__name__} 引数: {args} {kwargs}')
            start_time = time.time()
            result = func(*args, **kwargs)  # 内部で元の関数を呼び出す
            if show_output:
                print(f'実行結果: {result} 処理時間: {time.time()-start_time}')
            return result

        return wrapper  # 置き換えられた新しい関数を返す

    if func is not None:  # 括弧なし(デコレータとして)使用時
        return _logger(func)  # 同機能の関数wrapperを返す
    return _logger  # そうでない場合、括弧付き呼び出し(デコレータを返す関数として)使用時、デコレータ_loggerを返す



@logger  # デフォルトパラメータを使用する場合でも括弧不要
def add(a, b):
    return a + b

@logger()
def sub(a, b):
    return a - b

@logger(show_output=True)
def mul(a, b):
    return a * b

注意:1. デコレータlogger()を使用して最初のパラメータfuncを手動で指定することはできません 2. パラメータを指定する際は、key=value形式のみ使用可能です

タグ: Python デコレータ 関数装飾 プログラミング コード設計

6月7日 17:50 投稿