Pythonのデコレータを理解する

Pythonのデコレータを理解してみましょう。
デコレータとは関数の定義の前に@を宣言して対象の関数の機能を変化(デコレイト)させるものです。
@の後に記述した文字列の関数によってデコレータの処理が実行されます。
Javaではメソッド定義の前に@で記述するものをアノテーションと言いますが、Javaアノテーションは注記のような意味合いになり、Pythonのデコレータとは違うものになります。


この例では@some_decoratorによって関数fooがデコレイトされます。

@some_decorator  # ←これがデコレータ
def foo():
    return 1


実際にコードで確認してみましょう。

def decorate_func(func):
    print('called')

@decorate_func
def foo():
    return 1

「decorate_func」という関数を定義しています。これがデコレータになります。
「foo」という関数の定義の前に@で「decorate_func」を呼び出しています。
このコードを実行してみましょう。

>>> def decorate_func(func):
...     print('called')
...
>>> @decorate_func
... def foo():
...   return 1
...
called
>>>

「called」がコンソールに出力されました。
「decorate_func」が実行されたことが分かります。


このコードは、

@decorate_func
def foo():
    return 1

実際には下記のコードに変換され実行されることになります。

def foo():
    return 1

foo = decorate_func(foo)

Pythonでは関数もオブジェクトです。
「decorate_func」の引数として「foo」関数をオブジェクトとして渡すことが出来ます。

確認してみましょう。
「decorate_func」の中を変更します。

def decorate_func(func):
    print(type(func), func.__name__)

引数の「func」の型と名前を出力します。


実行すると、

>>> def decorate_func(func):
...     print(type(func), func.__name__)
...
>>> @decorate_func
... def foo():
...     return 1
...
<class 'function'> foo

 型は「function」、名前は「foo」と出力されました。
「foo」関数がオブジェクトとして「decorate_func」の引数に渡されて実行されたことが確認出来ます。

ただ、今のままだと「decorate_func」の戻り値がありませんので「foo」を関数として実行すると、Noneに対して関数呼び出しをする形になり「TypeError: 'NoneType' object is not callable」となります。
「decorate_func」の戻り値としてはデコレイトされた関数オブジェクトを返したいですね。


Pythonでは関数の中に関数を定義出来ます。
デコレータの中で関数を定義して、その関数をオブジェクトとして返します。

def decorate_func(func):
    def after_decorated():
        return 1 + func()  # 引数で受け取った関数オブジェクトを関数として実行して、その結果に1プラスします
    return after_decorated  # 関数をオブジェクトとして返す


以下実行してみます。

>>> def decorate_func(func):
...     def after_decorated():
...         return 1 + func()
...     return after_decorated
...
>>>
>>> @decorate_func
... def foo():
...     return 1
...
>>> print(foo())
2

1を返す「foo」の定義が「decorate_func」により2を返す関数に変化しました。