首先,宝宝觉着网上没有比我这还透彻的@functools.wraps分析了~~~害羞自恋ing……
栗子详情:http://stackoverflow.com/questions/308999/what-does-functools-wraps-do
step1:
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方法的假象= =!)
————————————————
突然发现的用法
出现问题:View function mapping is overwriting an existing endpoint function
原因:经过装饰器装饰的函数,函数名都是一样的,所以会报错
解决:给装饰器增加@functools.wraps