通俗易懂的分析——python装饰器之@functools.warps

首先,宝宝觉着网上没有比我这还透彻的@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

你可能感兴趣的:(python)