Python の二種類のデコレータ2022年05月26日 11時29分57秒

Python には二種類のデコレータがある。まずは、引数無しのデコレータ。そして、引数ありのデコレータ。

引数無しのデコレータと functools.wraps の解説があり、引数ありのデコレータの解説がある。

簡単にまとめると、デコレータとは関数を引数に実行される関数の呼び出し。実例を見た方が断然分かりやすい。コードは上記のところから引用。

デコレータのこの表記は、

@logged
def f(x):
   """does some math"""
   return x + x * x
以下の表記と同じ。
def f(x):
    """does some math"""
    return x + x * x
f = logged(f)
関数の実装を置き換えている。

この置き換え時に、Python の内部変数名も替えておくと色々と便利。そこで functools.wraps が使える。

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

print(f.__name__)  # prints 'f'
print(f.__doc__)   # prints 'does some math'

それ自体が引数を取るデコレータだとかなりややこしくなって、関数を返す関数を二重で行う必要がある。理屈としては、

@decorator_with_args(arg)
def foo(*args, **kwargs):
    pass
foo = decorator_with_args(arg)(foo)
に成るから、一段深くなる。

最終的に、以下の定型文になる。

def decorator_factory(argument):
    def decorator(function):
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            result = function(*args, **kwargs)
            more_funny_stuff()
            return result
        return wrapper
    return decorator

何度見ても折り込み等で混乱する。