ヒトリ歩き

愚痴とかいろいろ書きます

Pythonデコレータの実装方法

ロバストPythonでも出てきたが、自分でデコレータを作成してみた。引数ありなしで少し実装が変わるので注意したい。

デコレータ

デコレータは関数の前後に実行したい振る舞いを定義することができる。

コンテキストマネージャとの違いは?

コンテキストマネージャとデコレータは共に、処理の前後で何かしらの処理をしたいときに使うイメージがある。 私の理解だと下記の違いがあると考える。

  • 関数の前後で何かしらの処理をしたい場合に使用
  • ある特定の処理のブロックの前後で何かしらの処理をしたい場合に使用

コンテキストマネージャの場合、with文と使うことでファイルオープンしたオブジェクトをブロック内の処理に渡すことができるので、個人的にコンテキストマネージャの方が使いやすいという印象がある。

自前のデコレータを作るには

引数なしのデコレータと引数ありのデコレータでは実装が少し違うので、実際に作ってみる。

引数なしのデコレータ

下記の例の場合、callingデコレータを用意した。 callingデコレータとなる関数のパラメータにCallableを指定。これがデコレータを付与した関数が設定される。 _wrapper関数をcalling関数内で宣言し、_wrapper関数を返却する。
_wrapper関数は、パラメータとして、*args**kwargsを設定する。 *args**kwargsにデコレータを設定した関数のパラメータが設定される。
下記の例の場合、tanakaと 20*argsに設定されている。

"""
デコレータにする関数を宣言。パラメータはCallable
"""
def calling(func: Callable) -> Callable:
    """
    _wrapper関数を宣言。パラメータは*argsと**kwargs
    デコレータを付与した関数のパラメータが設定される。
    """
    def _wrapper(*args, **kwargs):
        print("calling decorator start ------->")
        print("args = " + str(args))
        # デコレータを付与した関数を実行
        func(*args, **kwargs)
        print("---------> calling decorator end")

    return _wrapper

@calling
def execute(name: str, age: int):
    print("execute start")

if __name__ == '__main__':
    execute("tanaka", 20)

実行すると下記のように、デコレータにした関数の前後のメッセージがデコレータを付与した関数の前後で出力されているのがわかる。

calling decorator start ------->
args = ('tanaka', 20)
execute start
---------> calling decorator end

引数ありのデコレータ

引数ありのデコレータの場合、デコレータにする関数の中で_wrapper関数を呼ぶ内部関数を定義する必要がある。 引数なしのデコレータとの違いは、 デコレータとなる関数から返す関数が_wrapper関数なのか、_wrapper関数をもつ内部関数を返すかの違い。

下記の場合、calling_taxデコレータを用意。パラメータがデコレータで指定するパラメータで戻り値はCallable。 calling_tax関数で内部関数を定義する。 この関数のパラメータはCallableで、デコレータを付与した関数が設定される。
内部関数の_calling_tax関数で_wrapper関数を用意する。ここは引数なしのデコレータと一緒。

"""
デコレータにする関数を宣言
パラメータはそのままデコレータのパラメータになる
"""
def calling_tax(name: str) -> Callable:
    """
    内部関数を宣言。パラメータはCallable
    このCallableは、デコレータを設定した関数が設定される
    """
    def _calling_tax(func: Callable):
        """
        _wrapper関数内で処理を実行
        *args、**kwargsがデコレータを付与した関数のパラメータの値が設定される
        """
        @functools.wraps(func)
        def _wrapper(*args, **kwargs):
            print("calling_tax decorator start ------>")
            print("name = " + name)
            print("args = " + str(args))
            func(*args, **kwargs)
            print("-------> calling_tax decorator end")
        return _wrapper
    return _calling_tax

@calling_tax("watanabe")
def execute_tax(address: str, tel: int):
    print("start execute_tax")
    print("address = " + str(address))
    print("tel     = " + str(tel))

if __name__ == '__main__':
    execute_tax("Kanagawa", 11111)

実行結果は下記のように、デコレータのパラメータも渡されており、デコレータを付与した関数の前後で設定したメッセージが出力されていることがわかる。

calling_tax decorator start ------>
name = watanabe
args = ('Kanagawa', 11111)
start execute_tax
address = Kanagawa
tel     = 11111
-------> calling_tax decorator end

まとめ

自分でデコレータを作成する方法を理解できた。 引数あり/なしで実装の仕方が少し違うので注意したい。
コンテキストマネージャとの使い分けも今後、考えながら自分でデコレータを用意して実装をシンプルに実現できるようにしていきたい。

参考

atmarkit.itmedia.co.jp

zenn.dev

note.nkmk.me