一个需求的实现
当前, 我们有这么一个小的需求: 通过装饰器来计算函数执行的时间
计算出这个函数的执行时长
- def add(x,y): # add = TimeIt(add)
- time.sleep(1)
- 'this is add'
- return x + y
装饰器实现
- 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__
改造
手动拷贝: 粗糙的改造方式, 将其__doc__ __name__强行复制到实例中
self 无非是我们当前所绑定的类实例, fn 是通过装饰器传递进来的 add, 我们将 fn 的 doc 和 name 作为源强行的赋值到 self 中, 如下:
- class TimeIt:
- def __init__(self,fn):
- print('init')
- self._fn = fn
- # 函数的 doc 拷贝到 fn 中
- self.__doc__ = self._fn.__doc__
- self.__name__ = self._fn.__name__
这样效果肯定是不好的, 这样做就是为了得知其保存位置, 那么接下来引入 wraps 模块
引入 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 中, 并使用偏函数生成了一个新的函数并返回
来源: http://www.bubuko.com/infodetail-2632750.html