前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python wraps那点儿事儿

python wraps那点儿事儿

作者头像
py3study
发布2020-01-08 15:32:44
2380
发布2020-01-08 15:32:44
举报
文章被收录于专栏:python3python3

 一个需求的实现

当前,我们有这么一个小的需求:通过装饰器来计算函数执行的时间

计算出这个函数的执行时长

代码语言:javascript
复制
def add(x,y):   # add = TimeIt(add)
    time.sleep(1)
    'this is add'
    return x + y

装饰器实现

代码语言:javascript
复制
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__)

我们所看到的信息如下:

代码语言:javascript
复制
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中,如下:

代码语言:javascript
复制
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本质是一个函数装饰器,通过接收一个参数再接收一个参数进行传递并处理,反正网上也一堆使用方法,举例不再说明,但是这里需要将函数调用的等价式摸清

使用方式:

代码语言:javascript
复制
from functools import wraps
def looger(fn):
    @wraps(fn)       
    def wrapper(*args, **kwargs):
        xxxxxxxx

  等价式关系 : @wraps(fn) = ( a = wraps(fn);  a(wrapper) )

 可以看出,源是传递进来的fn,目标是self,也就是wrapper

过程分析

首先我们通过编辑器跟进到函数内部

代码语言:javascript
复制
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 发现是被跳转到了

代码语言:javascript
复制
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  只有默认值,但是够我们用了 对象属性的访问 继续往下查看代码:

代码语言:javascript
复制
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
代码语言:javascript
复制
它是通过反射机制通过找到__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中,并使用偏函数生成了一个新的函数并返回
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/09/21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档