一个需求的实现python
当前,咱们有这么一个小的需求:经过装饰器来计算函数执行的时间app
计算出这个函数的执行时长编辑器
def add(x,y): # add = TimeIt(add) time.sleep(1) 'this is add' return x + y
装饰器实现ide
import time import datetime from functools import wraps class TimeIt: def __init__(self,fn): print('init') self._fn = fn def __call__(self, *args, **kwargs): start = datetime.datetime.now() ret = self._fn(*args, **kwargs) delta = datetime.datetime.now() - start print(delta) return ret @TimeIt def add(x,y): # add = TimeIt(add) time.sleep(1) 'this is add' return x + y add(1,2) print(add.__doc__) print(add.__name__)
咱们所看到的信息以下:函数
Traceback (most recent call last): File "H:/Python_Project/test2/3.py", line 33, in <module> print(add.__name__) AttributeError: 'TimeIt' object has no attribute '__name__'
那么问题来了,在打印__doc__ 和 __name__ 的时候看到返回的并不是是咱们想要的,由于已经被包装到TimeIt中的可调用对象,因此,如今它是一个实例了,实例是不能调用__name__的;因此,咱们来手动模拟一下,将其假装写入__doc__ 和 __name__this
改造spa
手动拷贝:粗糙的改造方式,将其__doc__ __name__强行复制到实例中对象
self无非是咱们当前所绑定的类实例,fn是经过装饰器传递进来的add,咱们将fn的doc 和 name 做为源强行的赋值到self中,以下:rem
class TimeIt: def __init__(self,fn): print('init') self._fn = fn # 函数的doc 拷贝到 fn中 self.__doc__ = self._fn.__doc__ self.__name__ = self._fn.__name__
这样效果确定是很差的,这样作就是为了得知其保存位置,那么接下来引入wraps模块get
引入wraps
wraps本质是一个函数装饰器,经过接收一个参数再接收一个参数进行传递并处理,反正网上也一堆使用方法,举例再也不说明,可是这里须要将函数调用的等价式摸清
使用方式:
from functools import wraps def looger(fn): @wraps(fn) def wrapper(*args, **kwargs): xxxxxxxx
等价式关系 : @wraps(fn) = ( a = wraps(fn); a(wrapper) )
能够看出,源是传递进来的fn,目标是self,也就是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(). """
可看到wraps中,须要传递几个参数,跟进到assigned,被包装的函数才是src源,也就是说被外部的更新掉
查看 assigned = WRAPPER_ASSIGNMENTS
经过WRAPPER_ASSIGNMENTS 发现是被跳转到了
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__') WRAPPER_UPDATES = ('__dict__',) def update_wrapper(wrapper, wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES):
可看到wraps中,须要传递几个参数,跟进到assigned,被包装的函数才是src源,也就是说被外部的更新掉
查看 assigned = WRAPPER_ASSIGNMENTS
那么赋值更新哪些东西呢?就是这些属性,以下所示
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
而updated = WRAPPER_UPDATES 所覆盖的则就是从WRAPPER_UPDATES = ('__dict__',)的基础上在执行了更新操做WRAPPER_ASSIGNMENTS,说白了全是在当前__dict__中进行
若是存在字典之类的属性要作的是并非覆盖字典,而是在他们的字典中将自身的信息覆盖或增长等更新操做
assigned 只有默认值,可是够咱们用了
对象属性的访问
继续往下查看代码:
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()
return wrapper
它是经过反射机制经过找到__dict__,若是存在则返回,没有则触发setattr将value写入到__dict__
value = getattr(wrapped, attr) 从attr反射获取了属性,attr就是assigent,而assigent就是WRAPPER_ASSIGNMENTS 定义的属性
setattr(wrapper, attr, value) 若是没有找到则动态的加入到其字典中
wrapper.__wrapped__ = wrapped 将wrapper拿到以后为其加入了一个属性,也属于一个功能加强,把wrapperd 也就是被包装函数,将add的引用交给了def wrapper(*args, **kwargs) ; 凡是被包装过的都会增长这个属性
说白了 wraps就是调用了update_wrapper,只不过少了一层传递
那么再回到wraps中(这下面为啥刷不出来格式?)
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
是否是感受少了些东西?实际它是调用了partial偏函数
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
经过偏函数,update_wrapper 对应的wrapper ,送入一个函数,其余 照单全收
接下来又会引入一个新的函数,partial具体分析后期再写
总之一句话:wraps 是经过装饰器方式进行传参并加强,将须要一些基础属性以反射的方式从源中赋值到当前dict中,并使用偏函数生成了一个新的函数并返回