设为首页 收藏本站
查看: 802|回复: 0

[经验分享] python wraps那点儿事儿

[复制链接]

尚未签到

发表于 2018-8-4 12:04:50 | 显示全部楼层 |阅读模式
  一个需求的实现
  当前,我们有这么一个小的需求:通过装饰器来计算函数执行的时间
  计算出这个函数的执行时长
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 &quot;H:/Python_Project/test2/3.py&quot;, 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):
  
    &quot;&quot;&quot;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().
  
    &quot;&quot;&quot;
  可看到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中,并使用偏函数生成了一个新的函数并返回

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-546452-1-1.html 上篇帖子: Python 利用socket 实现 ssh 跳转 下篇帖子: python简单的用户管理
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表