update_wrapper和wraps

前言

被装饰器 装饰过后的对象,其实已经不是原来的那个对象了,测试如下:

def dec(func):
    def inner(*args,**kwargs)->None:
        '''inner __doc__'''
        print('do something')
        func()
    return inner

@dec
def test(a:int):
    '''test __doc__'''
    pass

print(test.__doc__)		#inner __doc__
print(test.__name__)	#inner
print(test.__qualname__)#dec.<locals>.inner	
print(test.__annotations__)#{'return': None}

test函数已经成了 inner函数了,如果需要维持原来的test函数的这些 值,做法如下:

from functools import update_wrapper,wraps

def dec(func):
    def inner(*args,**kwargs)->None:
        '''inner __doc__'''
        print('do something')
        func()
    # 加一行代码
    update_wrapper(inner,func)
    return inner

@dec
def test(a:int):
    '''test __doc__'''
    pass

print(test.__doc__)
print(test.__name__)
print(test.__qualname__)
print(test.__annotations__)

输出如下:

test __doc__
test
test
{'a': <class 'int'>}

分析

如上测试结果,update_wrapper接收了两个参数,第一个为被修改属性的函数,第二个参数为提供修改值的函数,这样就把 第一个函数的部分属性改成了第二个函数的属性

查看update_wrapper源码

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):

    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, {}))
    wrapper.__wrapped__ = wrapped
    return wrapper

流程如下:

  • update_wrapper接收四个参数,第一个为要修改的对象,第二个为提供修改值的对象,assigned默认接收的值为:WRAPPER_ASSIGNMENTSupdated 默认接收的值为 WRAPPER_UPDATES
  • 遍历WRAPPER_ASSIGNMENTS,将wrapped的所有在该元祖的属性全部赋给wrapper,不在该元祖内的属性不会修改
  • 遍历WRAPPER_UPDATES,将wrapped的所有在该元祖的属性全部赋给wrapper,不在该元祖内的属性不会修改【默认是__dict__

所以回到举例中的 update_wrapper(inner,func) 将test的__moudle__ __name__ qualname__ __doc__ __annotations__ 的值覆盖了inner, 所以最后输出的是 test的这些值

wraps

获得和上述 update_wrapper相同的效果,使用wraps即可,输出的也还是test函数的原来的那些值

from functools import update_wrapper,wraps

def dec(func):
    #加个装饰器
    @warps(func)
    def inner(*args,**kwargs)->None:
        '''inner __doc__'''
        print('do something')
        func()
    return inner

@dec
def test(a:int):
    '''test __doc__'''
    pass

print(test.__doc__)
print(test.__name__)
print(test.__qualname__)
print(test.__annotations__)

查看wraps源码

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

接收三个参数,其中两个有默认值,另外一个为wrapped【提供修改属性的对象】, 返回一个偏函数对象,偏函数对象的 func为 update_wrapper,固定了参数wrapped,以及 assigned 和 updated。

这时候相当于wraps(test) 就是一个update_wrapper函数,然后作为装饰器装饰inner,将inner作为update_wrapper中wrapper[要被修改属性的对象]的实参,最后返回的inner其实是已经被update_wrapper处理过的函数

测试一下:

print(test.__wrapped__) # {'__wrapped__': <function test at 0x000001E09D1BA940>}

其实这里的test是inner,只是inner被修改了那些属性,然后这里的__wrapped__ 的值对应 update_wrapper源码中的wrapper.__wrapped__ = wrapped

总结

  • 写装饰器的时候,要保持被装饰的函数维持原来的属性,通用写法是在装饰器函数的内部函数前加上@wraps(外部函数的参数),通用模板如下:
from functools import wraps

def outer(par):
    @wraps(par)
    def inner(*args.**kwargs):
        print('do something')
        par()
    return inner
  • update_wrapper django cbv中有典型应用

    as_view的源码片段如下:

    # take name and docstring from class
    update_wrapper(view, cls, updated=())
    
    # and possible attributes set by decorators
    # like csrf_exempt from dispatch
    update_wrapper(view, cls.dispatch, assigned=())
    

posted @ 2022-08-01 15:53  Alantammm  阅读(206)  评论(0编辑  收藏  举报