Python のデコレーターについて理解するための個人的なまとめです。
デコレーター
@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>
よって代入できたりします。
上の hello
を passed_func
に代入します。 passed_func
は hello
オブジェクトを指します。実行もできます。
>>> passed_func = hello
>>> passed_func()
'hello'
>>> passed_func
<function hello at 0x000002350BF939D8>
また、hello
をdel
しても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
ここまでくると以下が理解できると思います。
@f1(arg)
@f2
def func(): pass
は大体次と等価です。
def func(): pass
func = f1(arg)(f2(func))