失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > python wraps_python wraps那点儿事儿

python wraps_python wraps那点儿事儿

时间:2018-08-22 11:47:57

相关推荐

python wraps_python wraps那点儿事儿

一个需求的实现

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

计算出这个函数的执行时长defadd(x,y):#add=TimeIt(add)

time.sleep(1)

'thisisadd'

returnx+y

装饰器实现importtime

importdatetime

fromfunctoolsimportwraps

classTimeIt:

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)

returnret

@TimeIt

defadd(x,y):#add=TimeIt(add)

time.sleep(1)

'thisisadd'

returnx+y

add(1,2)

print(add.__doc__)

print(add.__name__)

我们所看到的信息如下:Traceback(mostrecentcalllast):

File"H:/Python_Project/test2/3.py",line33,in

print(add.__name__)

AttributeError:'TimeIt'objecthasnoattribute'__name__'

那么问题来了,在打印__doc__ 和 __name__ 的时候看到返回的并非是我们想要的,因为已经被包装到TimeIt中的可调用对象,所以,现在它是一个实例了,实例是不能调用__name__的;所以,我们来手动模拟一下,将其伪装写入__doc__ 和 __name__

改造

手动拷贝:粗糙的改造方式,将其__doc__ __name__强行复制到实例中

self无非是我们当前所绑定的类实例,fn是通过装饰器传递进来的add,我们将fn的doc 和 name 作为源强行的赋值到self中,如下:classTimeIt:

def__init__(self,fn):

print('init')

self._fn=fn

#函数的doc拷贝到fn中

self.__doc__=self._fn.__doc__

self.__name__=self._fn.__name__

这样效果肯定是不好的,这样做就是为了得知其保存位置,那么接下来引入wraps模块

引入wraps

wraps本质是一个函数装饰器,通过接收一个参数再接收一个参数进行传递并处理,反正网上也一堆使用方法,举例不再说明,但是这里需要将函数调用的等价式摸清

使用方式:fromfunctoolsimportwraps

deflooger(fn):

@wraps(fn)

defwrapper(*args,**kwargs):

xxxxxxxx

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

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

过程分析

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

assigned=WRAPPER_ASSIGNMENTS,

updated=WRAPPER_UPDATES):

"""Decoratorfactorytoapplyupdate_wrapper()toawrapperfunction

Returnsadecoratorthatinvokesupdate_wrapper()withthedecorated

functionasthewrapperargumentandtheargumentstowraps()asthe

remainingarguments.Defaultargumentsareasforupdate_wrapper().

Thisisaconveniencefunctiontosimplifyapplyingpartial()to

update_wrapper().

"""

可看到wraps中,需要传递几个参数,跟进到assigned,被包装的函数才是src源,也就是说被外部的更新掉

查看 assigned = WRAPPER_ASSIGNMENTS

通过WRAPPER_ASSIGNMENTS 发现是被跳转到了WRAPPER_ASSIGNMENTS=('__module__','__name__','__qualname__','__doc__',

'__annotations__')

WRAPPER_UPDATES=('__dict__',)

defupdate_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中,并使用偏函数生成了一个新的函数并返回

如果觉得《python wraps_python wraps那点儿事儿》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。