前篇 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)
沒有留言:
張貼留言