通俗易懂的分析——python裝飾器之@functools.warps
首先,寶寶覺著網上沒有比我這還透徹的@functools.wraps分析了~~~害羞自戀ing……
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
it's exactly the same as saying
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
這時(劃重點!),your function f is replaced with the function with_logging.
print f.__name__ #with_logging
print f.__doc__ #什麼也沒有,因為with_logging沒有doc,f才有doc
因為f方法的name doc args也變了
簡言之:使用裝飾器時,原函式會損失一些資訊,因為指向裝飾器中的函式。(具體解釋看後邊的原理~)
所以,我們需要使用@functools.wraps,使用方法如step2
step2:
import functools
def logged(func):
@functools.wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print f.__name__ #'f'
print f.__doc__ #'does some math'
————————————————————————————
原理如下:
#...functools.py檔案...
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
#...這時wrapper已經有了wrapped的assignments和__dict__
return wrapper
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
#...其實是用partial對update_wrapper封裝,partial後邊會舉例子
return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)
這樣 我們再來看stackoverflow裡面的例子(我們展開step2中的@functools.wraps)
展開1(@functools.wraps(func)轉換成@_temp,裝飾器的語法糖替換)
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
_temp = functools.wraps(func)
with_logging = _temp(with_logging)
return with_logging
展開2(wraps(func)展開)
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
_temp = functools.partial(update_wrapper, wrapped=func, assigned=assigned, updated=updated)
#1、先了解partial函式
#這個函式意思是:我有個函式add_three_number(a,b,c)(三個數相加求和),a=2和b=5固定不變,c可變
#但是我不想每次呼叫都寫add_three_number(a=2,b=5,c=x),我只想寫sum_number(x)
#這樣就可以用partial函式封裝一下,sum_number=partial(add_three_number,a=2,b=5),每次呼叫sum_number(x)就好了
#partial封裝的update_wrapper函式賦值給了_temp,這時update_wrapper函式只有wrapper是可變的
with_logging = _temp(with_logging)
#2、_temp(with_logging)相當於把update_wrapper函式中的wrapper變數補齊~
return with_logging
展開3(partial展開)
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
with_logging = functools.update_wrapper(with_logging, func, assigned=assigned, updated=updated)
return with_logging
之後,update_wrapper函式將func(也就是f)的WRAPPER_ASSIGNMENTS = (‘module‘, ‘name‘, ‘doc‘) 和WRAPPER_UPDATES = (‘dict‘,)賦給返回函式(with_logging)
原理done
——————————————————————
哦哈哈哈~開森
(總結,敲黑板!)
So,f = logged(f)之後(等同於f加裝飾器@logged),f=with_logging,
情況1:裝飾器中沒有用@functools.wraps(func)。f直接指向with_logging方法就會出現下邊的情況:
print f.__name__ #with_logging
print f.__doc__ #什麼也沒有,因為with_logging沒有doc,f才有doc
情況2:裝飾器中用了@functools.wraps(func)。按原理中的解釋,一步一步,用update_wrapper方法把WRAPPER_ASSIGNMENTS和WRAPPER_UPDATES賦給with_logging。最後f還是指向with_logging方法,但with_logging方法多了f的屬性呀,所以出現正確結果:
print f.__name__ #'f'
print f.__doc__ #'does some math'
(造成你以為還在呼叫f方法的假象= =!)