@wraps作用
python中的裝飾器裝飾過的函數其實就不是函數本身了,我們可以看看下面的例子
import time
def timmer(func):
"""timmer doc"""
def inner(*args, **kwargs):
"""inner doc"""
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print("函數運行時間為 %s" % (end - start))
return res
return inner
@timmer
def func_test():
"""func_test doc"""
time.sleep(2)
return
print(func_test.__name__) # inner
print(func_test.__doc__) # inner doc
按我們正常的思維,func_test.__name__應該拿到的就是“func_test”,所以這個結果就印證了上面的第一句話,但是這是我們加一個@wraps,就會發現好像一切都正常了:
import time
from functools import wraps
def timmer(func):
"""timmer doc"""
@wraps(func)
def inner(*args, **kwargs):
"""inner doc"""
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print("函數運行時間為 %s" % (end - start))
return res
return inner
@timmer
def func_test():
"""func_test doc"""
time.sleep(2)
return
print(func_test.__name__) # func_test
print(func_test.__doc__) # func_test doc
@wraps的實現原理
為了方便理解,我把源碼和例子放在了一起,這樣的話我們看着會方便:
import time
from functools import partial
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper, # inner
wrapped, # func_test
assigned=WRAPPER_ASSIGNMENTS,
updated=WRAPPER_UPDATES):
"""Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
print('update_wrapper 執行...')
for attr in assigned:
try:
value = getattr(wrapped, attr)
except AttributeError:
pass
else:
setattr(wrapper, attr, value)
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Issue #17482: set __wrapped__ last so we don't inadvertently copy it
# from the wrapped function when updating __dict__
wrapper.__wrapped__ = wrapped
# Return the wrapper so this can be used as a decorator via partial()
print('update_wrapper 執行結束')
return wrapper
def wraps(wrapped,
assigned=WRAPPER_ASSIGNMENTS,
updated=WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
print('wraps 執行...')
print('wraps 執行結束') # 純粹為了打印出來的結果好理解
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
def timmer(func):
print('timmer 執行...')
@wraps(func) # inner = update_wrapper的返回值
def inner(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print("函數運行時間為 %s" % (end - start))
return res
print('timmer 執行結束') # 當然不是真正的結束,執行完下一行才結束
return inner
@timmer
def func_test():
print("func_test 執行...")
time.sleep(2)
print("func_test 運行結束")
return
func_test()
"""
打印結果如下:
timmer 執行...
wraps 執行...
wraps 執行結束
update_wrapper 執行...
update_wrapper 執行結束
timmer 執行結束
func_test 執行...
func_test 運行結束
函數運行時間為 2.0000197887420654
從打印的結果我們可以看出,@語法會在函數定義或者說模塊初始化階段(可能稱呼不對,以后回來改)就執行了
"""
上面的例子中我加了很多打印,主要是為了提醒一下在func_test()函數執行之前,@語法已經執行了。
其實原理很簡單,用了一個偏函數,去執行update_wrapper,真正起作用的也是這個函數,func_test執行之前,update_wrapper函數就會把inner函數的好多屬性(示例中WRAPPER_ASSIGNMENTS,WRAPPER_UPDATES指向的屬性 ,還有__wrapped__屬性)全部其換成func_test的屬性。
