2014-07-11

Python 的 decorator (Part II)


前篇 Python 的 decorator (Part I) 遺留了一個問題


如下程式碼, 那就是明明是 Star 為什麼 Star.__name__ 變成了 my_wrap ?

def ChrismasTree(func):
    def my_wrap(*args, **kwargs):
        print("pos 1 ....")
        print("pos 2 ....")
        out = func(*args, **kwargs)
        print("pos 3 ....")
        print("my name is: ", func.__name__)
    return my_wrap

# callee
@ChrismasTree
def Star(color):
    # with color do something ...
    print(color)
    return

Star("red")
print(Star.__name__)



還記得
@ChrismasTree
def Star(color):
    ...
    return
展開就等於 Star = ChrismasTree(Star) 嗎?
Star 當參數傳入 decorator, 被包裝後再由 Star 接收ChrismasTree的回傳值
而ChrismasTree的回傳值是  "return my_wrap"
沒錯, 它是回傳 my_wrap, 當然 Star 完完全全就是 my_wrap, 所以你在 Star 內的 code 若需要判斷 function name 就會出錯

解決
故 python 寫了一個  warpper 來解決這個問題
你只要在你的 decorator 下加一行, 如下紅字所示的指令 @wraps(func) 即可

from functools import wraps
# decorator
def ChrismasTree(func):
    @wraps(func)
    def my_wrap(*args, **kwargs):
        print("pos 1 ....")
        print("pos 2 ....")
        out = func(*args, **kwargs)
        print("pos 3 ....")
        print("my name is: ", func.__name__)
    return my_wrap

# callee
@ChrismasTree
def Star(color):
    # with color do something ...
    print(color)
    return

Star("red")
print(Star.__name__)



輸出

pos 1 ....
pos 2 ....
red
pos 3 ....
my name is:  Star
Star


成功了, 這樣我們就可以安心的活在 decorator 包裝的世界下而不會出錯了
至於原理是什麼, 其實很簡單就是再包一次, 但這裡就不再贅述, 有興趣的去 functools.py 看一下 update_wrapper 怎麼置換掉 ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')

題外話
又是 decorator 又是 wrapper, 其實它是要解決同一件事, 那為什麼 @ 會命名叫 decorator 而不是 wrapper 呢? 包裝跟點綴哪一個稱法較接近它實際的功能呢?

See More? 請看Python 的 decorator (Part III)


沒有留言:

張貼留言