🐍

PythonデコレータのSyntactic Sugarなぜ便利かを理解した

2022/11/23に公開

Pythonデコレータを利用する場合、@decoratorというSyntactic Sugarを関数やメソッドの先頭に付けるのが一般的ですが、なぜ便利なのかいまいち理解できていないので、調べてみました。

デコレータの詳細は公式ドキュメントあるいは他の方がすでに紹介されているので、本記事では割愛します。ちなみにおすすめの記事はこちらです。

https://rednafi.github.io/digressions/python/2020/05/13/python-decorators

まず適当に文字列の両側に<b>を追加してくれる簡単なデコレータを書きましょう。num_bは片方に追加する<b>の数を表しており、デフォルトは1となっています。

from functools import partial, wraps


class Emphasis:
    def __init__(self) -> None:
        pass

    def add_b(self, func=None, num_b=1):
        if func is None:
            return partial(self.add_b, num_b=num_b)

        @wraps(func)
        def wrap(*args, **kwargs):
            ret = func(*args, **kwargs)
            return "<b>" * num_b + ret + "<b>" * num_b

        return wrap

デコレータ引数なし・関数引数なしの場合

最初は一番簡単なパターンで見ていきましょう。デコレータep.add_b(hello)の返り値は関数なので、一回コール()すればSyntactic Sugarと同じことができるので、むしろSyntactic Sugarを使わないほうがわかりすそうですね。

ep = Emphasis()


def hello():
    return "Hello, There"


@ep.add_b
def hello_with_sugar():
    return "Hello, There"


print(ep.add_b(hello)())
print(hello_with_sugar())
<b>Hello, There<b>
<b>Hello, There<b>

デコレータ引数あり・関数引数ありの場合

しかしデコレータと関数両方引数がある場合はep.add_b(num_b=2)(hello)("Taro")を書かないといけません。言葉で説明すると、hello関数を引数としてデコレータep.add_b(num_b=2)に入れて返された関数に引数"taro"を渡してコールするという意味です。書けなくはないですが、ややこしくなるので、Syntactic Sugarのほうが断然簡単ですね

ep = Emphasis()


def hello(name):
    return f"Hello, {name}"


@ep.add_b(num_b=2)
def hello_with_sugar(name):
    return f"Hello, {name}"


print(ep.add_b(num_b=2)(hello)("Taro"))
print(hello_with_sugar("Taro"))
<b><b>Hello, Taro<b><b>
<b><b>Hello, Taro<b><b>

おわりに

Syntactic Sugarのおかげで、デコレータと関数両方引数がある場合でも簡単にデコレータを使えるようになりました。ただ個人的にデコレータを初めて触る方はまずあえてSyntactic Sugarを使わず試してみたら、デコレータの理解が捗るのではないかと思います。
ご参考になれば幸いです。

Discussion