python wraps那点儿事儿

 一个需求的实现

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

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

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://blog.51cto.com/yijiu/2125310

时间: 2024-10-20 11:22:42

python wraps那点儿事儿的相关文章

关于‘文件存储格式’和‘文件内容格式’需要掰一掰的那点儿事儿

太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 先来说个例子,来引用题中的问题,后面会尽可能列出大把的实际存在的关于这俩家伙的事儿,以供参考. 当我们录了一段语音并存成 .wav 文件后,就可以

30分钟带你熟练性能优化的那点儿事儿(案例说明)

前言 性能优化是数据库运维人员和中.高级软件开发人员的必备技能,很多时候老司机和新司机的区别就在写出的东西是否优化. 博主接触过近千家客户的系统,这些系统都存在着各种各样的性能问题.那么如何透彻的了解我们的数据库性能问题?今天就用一个案例来说明性能优化的那点儿事儿. PS:很多技术人员对优化有一套自己的理解,在阅读本文前请放下你自己的理解. 正所谓:跟着博主不迷路,博主带你上高速! 点开案例跟着博主的思路看看优化这些事儿 : 本文案例Demo 了解系统环境 优化首先要知道数据库在一个什么样的硬件

Python小知识点儿

2020/01/30 Python小知识点儿 ?1.python逻辑运算符 and or not 其中not使用的两个场景: ①在开发中,通常希望某个条件不满足时,执行一些代码,可以使用 not ②另外,如果需要拼接复杂的逻辑计算条件,同样也有可能使用到 not ?2. age=2 print("你%d岁了"%age) 输出结果: 你2岁了 ?3. ?4. ?5.随机数 ?6. ?7. 未完待续... Python小知识点儿 原文地址:https://www.cnblogs.com/L

还能不能愉快地起一个web服务啦?——1st Step!白话http和代码交互的那点儿事儿~

学写python的时候,我们多多少少都接触到了web程序,然而你有没有想过,当浏览器发送了一个http请求时,等待接收这个请求的后端代码是一种什么样的思想感情? 就像下面这张图里画的一样,后端也许是一段java代码,也许是php代码,当然,如果代码出自我手,最可能的还是一段python代码.这就好比,当你在社交软件上给一个陌生人发了一条消息,对面这个人可能是英国人.印度人也有可能是一只羊或者一只狗...那么问题来了,他们怎么知道你在说啥?当然了,在web服务的世界里没有现实中那么复杂,我们不需要

1.跟老韩学Python之工具那些事儿

1.关于安装Python3及版本选择安装Python在这里就不做讨论了,建议读者使用Python3版本,如果您是Linux系统管理人员,强烈建议您安装ipython,当然也是3系列的版本. 安装完成之后,可以使用如下代码确认Python3是否安装成功. [[email protected]_server ~]# python3 Python 3.6.8 (default, Apr 25 2019, 21:02:35) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]

面向对象那点儿事儿

长期以来,一直听大家都在说面向对象和面向过程,一直也没太懂什么意思,一番研究过后好像有一点懂了,个人见解,说错请帮我纠正. 所谓面向过程,打个比方,就好像你生病了,你也不知道到底什么病,反正医生说了,第一步你要先吃两颗药丸,第二步你要打一针,第三步你留下来输液吧.这整个流程走完,恭喜你,你的病好啦,这事儿也就算完,而像这样分步骤一步两步三步的去治病,整个过程按流程走,这就是一个面向过程的思想. 那什么又是面向对象呢?就好像今天有一群人生病了,来到医院,医生让这一群人排排站,然后拎出来三个人,对护

python多进程那点事儿【multiprocessing库】

前言:项目中有个需求需要对产品的日志处理,按照产品中日志的某些字段,对日志进行再次划分.比如产品的日志中含有字段id,tag=1,现在需要把tag是基数的放到一个文件中,tag是偶数的放入一个文件中.这就涉及到多个文件的读写操作,一个文件一个文件读取写入那时间太久了,公司配备的单机,跑了半个多小时,光标还是一直在闪闪闪[你懂得].没办法了,还是用多进程跑吧.这就得对python中的多进程从新回顾一遍了. Q1:为什么不用多线程呢? A1:这个就需要了解python多线程的实现原理了,通过在其解释

python wraps

用代码说明问题: def d(f): def _d(*args, **kwargs): print f.__name__, ' is called' f(*args, **kwargs) return _d @d def test(): """function test""" print 'test' if __name__=='__main__': print test.__name__, " | ", test.__doc

python wraps装饰器

这是一个很有用的装饰器.看过前一篇反射的朋友应该知道,函数是有几个特殊属性比如函数名,在被装饰后,上例中的函数名foo会变成包装函数的名字 wrapper,如果你希望使用反射,可能会导致意外的结果.这个装饰器可以解决这个问题,它能将装饰过的函数的特殊属性保留. import time import functools def timeit(func): @functools.wraps(func) def wrapper(): start = time.clock() func() end =t