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_ASSIGNMENTS
,updated
默认接收的值为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=())