ポイント
Pythonの以下性質を利用
・関数もオブジェクト
・関数自体を引数や戻り値として利用可能
利用イメージは以下の通り
・関数Aに引数として、関数Bを指定
・戻り値として、関数Bの機能に関数Aの機能を追加した関数を返す。(関数宣言時点で動作する。※後述)
コードのイメージ
関数base_funcに機能を追加する場合、以下の記述が可能
base_func = add_func(base_func)
このシンタックスシュガー(簡略記述)が関数デコレータ。以下記述が可能。
@add_func
def base_func():
処理
サンプルコード
関数デコレータなしで記述した場合
コード
def add_func(f):
def func():
f()
print("add func called")
return func
def base_func():
print("base func called")
print("# 機能追加前")
base_func()
print("# 機能追加後")
base_func = add_func(base_func)
base_func()
実行結果
# 機能追加前
base func called
# 機能追加後
base func called
add func called
関数デコレータで記述した場合
コード
def add_func(f):
def func():
f()
print("add func called")
return func
@add_func
def base_func():
print("base func called")
base_func()
実行結果
base func called
add func called
着目すべき動作
コード
def add_func(f):
print("test")
def func():
f()
print("add func called")
return func
@add_func
def base_func():
print("base func called")
実行結果
test
★重要。おさえるべき動き★
base_funcは実行してないが、testが出力される。
base_func定義が宣言された時点で、add_funcが実行され、add_funcのreturnが、base_funcとして登録される。
サンプルコードの問題点
オブジェクトの属性情報(関数名等)が変わってしまう。
さっきのコードの最後の行に以下を追加すると、、、
print(base_func.__name__)
実行結果は以下になる。
base func called
add func called
func
関数名が、add_func内側で定義した関数名になっている。
これを回避するには、func関数をfunctoolsモジュールのwraps関数でデコレートする。
コード
from functools import wraps
def add_func(f):
@wraps(f)
def func():
f()
print("add func called")
return func
@add_func
def base_func():
print("base func called")
base_func()
print(base_func.__name__)
実行結果
base func called
add func called
base_func
引数付き関数のデコレータ
コード
rom functools import wraps
def add_func(f):
@wraps(f)
def func(a):
f(a)
print("add func called")
return func
@add_func
def base_func(a):
print("base func called " + a)
base_func("hoge")
print(base_func.__name__)
実行結果
base func called hoge
add func called
base_func
おぼえておくべき点
・base_funcを実行すると、add_funcの第一引数(f)としてbase_funcが渡される。
・base_func実行時の引数は、add_func内で定義した関数(func)の引数(a)にセットされる。
デコレータ記述(@add_func)に引数をつける事も可能
add_func関数内でネストする必要あり。コードは以下の通り。
コード
from functools import wraps
def add_func(b):
def func1(f):
@wraps(f)
def func2(a):
print(b)
f(a)
print("add func called")
return func2
return func1
@add_func("moge")
def base_func(a):
print("base func called " + a)
base_func("hoge")
print(base_func.__name__)
実行結果
moge
base func called hoge
add func called
base_func
base_funcはadd_funcデコレータでfunc1で登録されるが、func1はfunc2をreturnする。
base_fund実行時は、結局func2が実行される。
最後に
・参考URLに記載の通りビジネスロジックと管理ロジックを分けるために有用
・ただ、自分はインフラSEだし、そんな本格的なコードは書いてません。。。。。関数に渡されたオブジェクトをデバッグとしてログに出したい時などに使えるかも
サンプルコード
def deco(f):
def func(*args):
print("#DEBUG " + f.__name__ + " args")
print(args)
f(*args)
return func
@deco
def test1(a, b):
print("HOGE!!")
@deco
def test2(b):
print("MOGE!!")
def main():
test1(1,2)
test2(3)
main()
実行結果
#DEBUG test1 args
(1, 2)
HOGE!!
#DEBUG test2 args
(3,)
MOGE!!