[Python] デコレーターについて

Python のデコレーターについて理解するための個人的なまとめです。

デコレーター

8.6. 関数定義

@f1(arg)
@f2
def func(): pass

は大体次と等価です。

def func(): pass
func = f1(arg)(f2(func))

???

デコレーターを使うことで、ある関数の内容は変更せずに、その関数の前後に別の処理を簡単に追加できるようになります。

関数はオブジェクトである

Python では、関数はオブジェクトです。

>>> def hello():
...     return 'hello'
...
>>> hello()
'hello'
>>> hello
<function hello at 0x000002350BF939D8>

よって代入できたりします。

上の hellopassed_func に代入します。 passed_funchello オブジェクトを指します。実行もできます。

>>> passed_func = hello
>>> passed_func()
'hello'
>>> passed_func
<function hello at 0x000002350BF939D8>

また、hellodel してもpassed_func は動きます。

>>> del hello
>>> hello
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'hello' is not defined
>>> passed_func
<function hello at 0x000002350BF939D8>
>>> hello()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'hello' is not defined
>>> passed_func()
'hello'

関数の中に関数を定義する

関数の中に関数を定義して、関数の中でその定義した関数を実行できます。

>>> def outer_func():
...     print('outer_func started')
...
...     def inner_func():
...         return '\t This is a inner func'
...
...     print(inner_func())
...     print("outer_func finished")
...
>>> outer_func()
outer_func started
         This is a inner func
outer_func finished

関数を返す関数を定義する

関数の中に関数を定義して、定義した関数を返す関数を作ることができます。

この関数を実行すると、中で定義した関数のオブジェクトが返ってきます。

>>> locals
<built-in function locals>
>>> def outer_func():
...     print('outer_func started')
...
...     def inner_func():
...         return '\t This is a inner func'
...
...     return inner_func
...
>>> outer_func()
outer_func started
<function outer_func.<locals>.inner_func at 0x000002350BF93F78>

返ってきた関数は、当然()をつけることでそのまま実行することができます。

>>> outer_func()()
outer_func started
'\t This is a inner func'

また、代入してから実行することもできます。

>>> passed_func = outer_func()
outer_func started
>>> passed_func()
'\t This is a inner func'

関数を引数として受け取る関数

また、関数を引数として受け取る関数を作ることもできます。

>>> def arg_func():
...     print('\t This is a func passed as arguments')
...
>>> def hello(func):
...     print('hello func started.')
...     print(func())
...     print('hello func finished.')
...
>>> hello(arg_func)
hello func started.
         This is a func passed as arguments
None
hello func finished.

None が返ってきていますが、これは Python では、returnのない関数は None を返すと決まっているためです。

デコレーターの実装

デコレートする関数 decorator_func(func) とデコレートされる関数decorated_func() をそれぞれ定義します。

>>> def decorator_func(func):
...     def wrap_func():
...         print('some codes executed.')
...         func()
...         print('other codes executed')
...     return wrap_func
...
>>> def decorated_func():
...     print('\t I must be decorated...')
...

デコレートされる関数decorated_func() を単独で実行してみます。

>>> decorated_func()
         I must be decorated...

デコレートされる関数decorated_func() をデコレートする関数 decorator_func(func) に引数として渡し実行します。

>>> decorator_func(decorated_func)
<function decorator_func.<locals>.wrap_func at 0x000002350BFA14C8>

decorator_func(func) の中で定義している wrap_func() 関数が返ってきています。

この返ってきた関数を実行します。

>>> decorator_func(decorated_func)()
some codes executed.
         I must be decorated...
other codes executed

wrap_func() が実行されることで、 wrap_func() の中のdecorated_func() も実行されました。

返ってきている関数は、変数に代入して実行することもできます。

>>> passed_func = decorator_func(decorated_func)
>>> passed_func()
some codes executed.
         I must be decorated...
other codes executed

このように書くと長くなるので、Python では簡単にデコレーターが実装できるように @ を使います。

まず、デコレーターを記述します。

>>> def decorator_func(func):
...     def wrap_func():
...         print('some codes executed.')
...         func()
...         print('other codes executed')
...     return wrap_func
...

デコレートされる関数の定義の上に、@デコレートする関数 を付けると、以下のように処理が行われます。

>>> @decorator_func
... def decorated_func():
...     print('\t I must be decorated...')
...
>>> decorated_func()
some codes executed.
         I must be decorated...
other codes executed

複数のデコレーターを使う

複数のデコレーターを使うこともできます。

>>> def decorator_func_inner(func):
...     def wrap_func():
...         print('\t INNER: some codes executed.')
...         func()
...         print('\t INNER: other codes executed')
...     return wrap_func
...
>>> def decorator_func_outer(func):
...     def wrap_func():
...         print('OUTER: some codes executed.')
...         func()
...         print('OUTER: other codes executed')
...     return wrap_func
...
>>> @decorator_func_outer
... @decorator_func_inner
... def decorated_func():
...     print('\t\t I must be decorated...')
...
>>> decorated_func()
OUTER: some codes executed.
         INNER: some codes executed.
                 I must be decorated...
         INNER: other codes executed
OUTER: other codes executed

デコレートされる関数に引数を渡す

下のように書くと、デコレートされる関数に引数を渡すことができます。

func_needs_decorator(str) の引数str が、wrap_func(str) に渡り、func(str) に渡ります。

>>> def decorator_func(func):
...     def wrap_func(str):
...         print('some codes executed.')
...         func(str)
...         print('other codes executed')
...     return wrap_func
...
>>> @decorator_func
... def func_needs_decorator(str):
...     print(str)
...
>>> func_needs_decorator('\t I must be decorated...')
some codes executed.
         I must be decorated...
other codes executed

デコレーター側で可変長引数を使うと汎用性が広がります。

>>> def decorator_func(func):
...     def wrap_func(*args, **kwargs):
...         print('some codes executed.')
...         func(*args, **kwargs)
...         print('other codes executed')
...     return wrap_func
...
>>> @decorator_func
... def func_needs_decorator(str):
...     print(str)
...
>>> func_needs_decorator('\t I must be decorated...')
some codes executed.
         I must be decorated...
other codes executed

デコレーターに引数を渡す

デコレーターに引数を渡すこともできます。

>>> def  decorator_func(str):
...     def _decorator_func(func):
...         def wrap_func():
...             print('decorator argument: ', str)
...             func()
...             print('other codes executed')
...         return wrap_func
...     return _decorator_func
...
>>> @decorator_func('argument')
... def func_needs_decorator():
...     print("\t This function is in need of a Decorator")
...
>>> func_needs_decorator()
decorator argument:  argument
         This function is in need of a Decorator
other codes executed

ここまでくると以下が理解できると思います。

8.6. 関数定義

@f1(arg)
@f2
def func(): pass

は大体次と等価です。

def func(): pass
func = f1(arg)(f2(func))