2014-07-12

Python 的 decorator (Part III)

問題
前面 decorator Part I & Part II 已經涵蓋了 decorator 困難部分8成
這裡我們再進階看一個 "參數問題"

Decorator 的下一個問題是, 如果我 decorator 要傳參數的話要怎麼辦呢?
個人覺得這個架構是 python 一個很醜很醜的敗筆

請看一下底下例子

無參數版

from functools import wraps

def first_fun(p1):
    print("in first_fun")
    print("p1=", p1)

    def second_fun(p2, *args, **kwargs):
        print("in second_fun")
        print("p2=", p2)
        print("args=", args)
        print("kwargs=", kwargs)
        p2("hello decoration!!", *args, **kwargs)
    return second_fun
#無參數, function name only

@first_fun
def decoration(decor_para):
    print("in decoration")
    print(decor_para)
    return



輸出結果

in first_fun
p1= <function decoration at 0x7f557697d620>

查一下 decoration 是什麼型態
>>> type(decoration) 
<class 'function'>

* 從output 的 p1 得知, 當 decorator 沒有參數時(只是function/class 名稱時), 預設會有1個傳入值, 而此傳入值即 function decoration
* p1 是 decoration, 故 second_fun 即 nothing, 跟 decorator 無關, 故沒有被執行到



有參數版
這裡我們保留大部分 code, 只有改成 decorator 需要傳參數以方便比對

from functools import wraps

def first_fun(p1):
    print("in first_fun")
    print("p1=", p1)

    def second_fun(p2, *args, **kwargs):
        print("in second_fun")
        print("p2=", p2)
        print("args=", args)
        print("kwargs=", kwargs)
        p2("hello decoration!!", *args, **kwargs)
    return second_fun
# 有參數, 第一個參數是字串"first_func parameter"
@first_fun("first_func parameter")
def decoration(decor_para):
    print("in decoration")
    print(decor_para)
    return



輸出結果

in first_fun
p1= first_func parameter
in second_fun
p2= <function decoration at 0x7fce990862f0>
args= ()
kwargs= {}
in decoration
hello decoration!!

查一下 decoration 是什麼型態
>>> type(decoration) 
<class 'NoneType'>
驚!什麼? decoration 是 None!

* 這裡我們發現, decorator 傳入的第1個參數 p1 不像第一個例子(decoration), p1 的內容物變成是 decorator 中你傳入的參數值了
* 然後 second_fun 的第1個參數 p2 卻變成 decoration


小結
所以這裡你就知道為什麼我們在無參數版就要放 second_fun 這個根本執行不到的 code 了吧, 程式碼都一樣, 只有有參數跟無參數的區別.

由以上結論得知, 你若要 decorator 可傳送參數, 那麼你的 decoration 只能在第二層function(second_fun) 獲得, 故你的樹(decorator)要寫兩層 function 以便獲得 decoration. 在建構你的樹的時候, 何時可以拿到 decoration 是很重要的一件事.
所以當你看到底下這堆程式碼時, 千萬不要被它嚇到了
它只不過是又要傳參數, 又要改 function name, 又要作 decorator 而已, 落落長的程式碼根本是嚇唬人而已. 其實做的功能並不多.

def render_to(tpl):
    def decorator(func):
        @wraps(func)
        def wrapper(request, *args, **kwargs):
            out = func(request, *args, **kwargs)
            if isinstance(out, dict):
                out = render_to_response(tpl, out, RequestContext(request))
            return out
        return wrapper
    return decorator


進階問題
那麼, first_fun 有二個參數的話呢?
@first_fun("first_func parameter 1", "first_func parameter 2")
試試看吧, 反正就是 try and see




2 則留言:

  1. 本文中所有的"Star("red")" 是否應該改成 decoration("red")??
    因為您在本文中的function 名字已經變了?

    回覆刪除
    回覆
    1. Star("red") 應該要全部刪除,我當初應該只是想表達參數的傳入規則而已,當初誤植,我已經刪除,並且加入 type() 去檢視 decoration 到底獲得什麼型態

      刪除