Python

pythonの引数付きデコレータをちょっと書きやすくする

はじめに

pythonの特徴の一つにデコレータがあります。
主に関数のラッパ関数を作るときのシンタックスシュガーとして使われ、Flask等のライブラリを使うとその良さがわかります。
(より正確には関数を引数に取る関数を書く際のシンタックスシュガーであり、工夫次第でいろいろできます。マーキングとか。)

デコレータは使うとき便利なのですが、書くときがややこしいです。
特に引数をとるデコレータを書くとき。通常はこんな感じになります。

def add_print(prefix): # デコレータの引数にprefixをとりたい
    def _add_print(func): # 引数付きデコレータが返す、引数なしデコレータ
        def wrapper(*args, **kwargs): # 引数なしデコレータが返す、ラッパ関数
            # prefix変数も使って、やりたかった処理をする
            print(f"{prefix}: {func.__name__} is invoked.")
            func(*args, **kwargs)
        return wrapper
    return _add_print

これ、ネストが深めで、デコレータに渡したい引数も場所がややこしくて、なんだか嫌ですよね。

ちょっと書きやすくする

そこで、ちょっと書きやすくできるデコレータを作ってみました。

def paramdeco(func):
    def param(*args, **kwargs):
        def wrapper(f):
            return func(f, *args, **kwargs)
        return wrapper
    return param

これを使って引数付きデコレータを書くと以下のようになります。

@paramdeco # 作りたい引数付きデコレータに@paramdecoをつける
def add_print(func, prefix): # 引数付きデコレータは第一引数を関数とし、第二引数以降を取りたい引数にする
    def wrapper(*args, **kwargs): # 引数なしデコレータ同様にラッパ関数を定義する
        print(f"{prefix}: {func.__name__} is invoked.")
        func(*args, **kwargs)
    return wrapper

@add_print("inspect")
def greeting(v):
    print(f"hello {v}!")

greeting("param deco")
# >>> inspect: greeting is invoked.
# >>> hello param deco!

引数付きデコレータをdef一発で定義できるので、引数なしデコレータと同様の文法になりました。
これで"ちょっと"だけ書きやすくなりました。

参考記事

Pythonのデコレータについて @mtb_beta
Python デコレータ再入門  ~デコレータは種類別に覚えよう~ @macinjoke