Python 入門 ノート (46)デコレーター

デコレーター 関数の修飾

簡単なfunctionを書きます。aとbを加える関数です。a + b = 30 答えは30です。

def add_num(a, b):
    return a + b
r = add_num(10, 20)
print(r)
30

 

関数を実行する前に、startとendを出力します。

def add_num(a, b):
    return a + b
print('start')
r = add_num(10, 20)
print('end')
print(r)
start
end
30

関数がreturnされる前に、start,endがprintされました。

デコレーター

これを、デコレーターでより簡単にします。

何かしら、関数を実行する前と後とか、またはadd_num functionに機能を付け加えたいときにデコレーターを使います。

最初に、デコレーター関数print_infoを書きます。
引数には関数オブジェクト(ここではfunc)を書きます。

*関数オブジェクト…fuction()と書かずに、()を省きfunctionのみ書きます。
これは、前者処理を実行し結果を返すのに対し、
後者は、処理の内容(プロパティ)自体を意味し、変数へ代入出来たり他の関数の引数になり得たりします。
wrapper()の引数を*args,**kwargsとする事により、なんでも引数に取れる様になります。

このfuncはインナーファンクションであるwrapper()の中で実行されreturnされます。

def print_info(func):
    def wrapper(*args, **kwargs):   
        print('--start--')
        result = func(*args, **kwargs)
        print('--end--')
        return result
    return wrapper
wrapper()の引数を*args,**kwargsとする事により、なんでも引数に取れる様になります。

関数print_infoの引数funcは関数オブジェクトで、func = add_numを意味します。

@print_info
def add_num(a, b):     
    return a +b

r = add_num(10, 20)  
print(r)  

#add_numを実行する際に、上のprint_infoに行き、innner関数のwrapperを呼び出します
wrapper内でfunc = add_numなので仮引数a,bをタプル*argsをアンラップした中に入れられ、print(r)により、実引数(10, 20)が渡され、10+20がreturnされます。

結果は、

--start--
--end--
30

そこで、次のように関数sub_num()を追記しても、計算してくれます。

def print_info(func):
    def wrapper(*args, **kwargs):
        print('--start--')
        result = func(*args, **kwargs)
        print('--end--')
        return result
    return wrapper

@print_info
def add_num(a, b):
    return a + b
r = add_num(10, 20)
print(r)

@print_info
def sub_num(a, b):
    return a - b
s = sub_num(100, 60)
    print(s)
--start--
--end--
30
--start--
--end--
40

一度デコレーターを書いておけば、@print_infoとするのみで、べつのsub_num()関数を簡単に追記できます。

順序に注意!

デコレーターが複数ある場合、デコレーターを@で呼び出す順序により結果が異なることに注意が必要です。

ここで、以下の様にするとどうなるか見てみます。
まず、
1)print_info()を書き、次にprint_more()を書きます。
次に、順序を逆にし
2)print_more()を書き、print_info()を書きます。

 

1)

def print_more(func):     
    def wrapper(*args, **kwargs):         
        print('func:', func.__name__)         
        print('args:', args)         
        print('kwargs:', kwargs)         
        result = func(*args, **kwargs)         
        print('result:', result)         
        return result     
    return wrapper 

def print_info(func):     
    def wrapper(*args, **kwargs):         
        print('--start--')         
        result = func(*args, **kwargs)         
        print('--end--')         
        return result     
    return wrapper 
@print_info 
@print_more 
def add_num(a, b): 
    return a + b 
r = add_num(10, 20) 
print(r)

結果は

--start--
func: add_num
args: (10, 20)
kwargs: {}
result: 30
--end--
30

となります。次にここで

@print_infoと@print_moreの順序を逆にしてみると、結果が異なります。

2)

def print_more(func):
     def wrapper(*args, **kwargs):
         print('func:', func.__name__)
         print('args:', args)
         print('kwargs:', kwargs)
         result = func(*args, **kwargs)
         print('result:', result)
         return result
     return wrapper

 def print_info(func):
     def wrapper(*args, **kwargs):
         print('--start--')
         result = func(*args, **kwargs)
         print('--end--')
         return result
     return wrapper

 # @print_info
 @print_more
 @print_info
 def add_num(a, b):
    return a + b

 r = add_num(10, 20)
 print(r)

 

func: wrapper
args: (10, 20)
kwargs: {}
--start--
--end--
result: 30
30

上記の様に、@print_infoと@print_moreの順序で結果が異なることに要注意です!

イメージとしては
@print_info
@print_more
の場合
add_numをprint_moreで包み込み、更にprint_infoで包み込むイメージで押さえておきます。

イメージ…print_info(print_more(add_num))

コメント