当前位置: 首页 > 科技观察

Python装饰器基础

时间:2023-03-15 13:51:07 科技观察

Text一般来说,装饰器就是一个函数,它接受一个函数(或类)作为参数,返回值也是一个函数(或类)。先看一个简单的例子:#-*-coding:utf-8-*-deflog_cost_time(func):defwrapped(*args,**kwargs):importtimebegin=time.time()try:returnfunc(*args,**kwargs)finally:print'func%scost%s'%(func.__name__,time.time()-begin)returnwrapped@log_cost_timedefcomplex_func(num):ret=0foriinxrange(num):ret+=i*ireturnret#complex_func=log_cost_time(complex_func)if__name__=='__main__':printcomplex_func(100000)codesnippet0代码,函数log_cost_time是一个装饰器,它的功能也很简单,打印被装饰函数的运行时间。装饰器的语法如下:@decdeffunc():pass本质上等同于:func=dec(func)。在上面的代码(代码片段0)中,将第12行注释掉,然后去掉第18行的注释,效果是一样的。另外,staticmethod和classmethod是我们在代码中经常用到的两个装饰器。如果对pyc进行反编译,得到的代码一般是func=staticmthod(func)。当然,@符号的形式更受欢迎,至少可以少拼一次函数名。装饰器可以嵌套,比如@dec0@dec1deffunc():pass将是func=dec0(dec1(fun))。装饰器也有“副作用”。对于用log_cost_time修饰的complex_calc,我们检查complex_func.__name__,输出为:“wrapped”。好吧,这是log_cost_time中的内部函数(包装)的名称。当然,调用者希望输出是“complex_func”。为了解决这个问题,python提供了两个函数。functools.update_wrapper原型:functools.update_wrapper(wrapper,wrapped[,assigned][,updated])第三个参数,直接将wrapped值复制到wrapper中,默认为(__doc__,__name__,__module__)第四个参数,update,默认是(__dict__)unctools.wraps:packageofupdate_wrapper这是一个方便的函数,用于在定义包装函数时调用partial(update_wrapper,wrapped=wrapped,assigned=assigned,updated=updated)作为函数装饰器。只需更改代码:importfunctoolsdeflog_cost_time(func):@functools.wraps(func)defwrapped(*args,**kwargs):importtimebegin=time.time()try:returnfunc(*args,**kwargs)finally:print'func%scost%s'%(func.__name__,time.time()-begin)returnwrapped然后看complex_func.__name__的输出是“complex_func”装饰器也可以带参数。我们稍微修改一下上面的代码:deflog_cost_time(stream):definner_dec(func):defwrapped(*args,**kwargs):importtimebegin=time.time()try:returnfunc(*args,**kwargs)finally:stream.write('func%scost%s\n'%(func.__name__,time.time()-begin))returnwrappedreturninner_decimportsys@log_cost_time(sys.stdout)defcomplex_func(num):ret=0foriinxrange(num):ret+=i*ireturnretif__name__=='__main__':printcomplex_func(100000)codesnippet1log_cost_time函数也接受一个参数,用于指定输出信息流,用于decorator@dec(dec_args)deffunc(*args,**kwargs):passwithparameters等同于func=dec(dec_args)(*args,**kwargs)。装饰器对类的装饰也很简单,但平时用的不多。比如我们需要修改类的__str__方法,代码很简单。defHaha(clz):clz.__str__=lambdas:"Haha"returnclz@HahaclassWidget(object):'''classWidget'''if__name__=='__main__':w=Widget()printw那么在什么场景下需要使用装饰器?,设计模式中有一种模式也叫装饰器。让我们简单回顾一下设计模式中的装饰器模式,简单一句话概述Dynamicallyaddadditionalresponsibilstoanobject由于装饰器模式只是从外部改变组件,组件不需要知道任何关于它的装饰;也就是,这些装饰对于组件来说是透明的。下图来自GOF的《设计模式Java手册》或《设计模式》。回到Python,自然要使用装饰器语法来实现装饰器模式。例如文章中的示例代码在不改变装饰对象的情况下增加了录音功能的执行时间。附加功能。当然,由于Python语言的灵活性,装饰器可以修改被装饰对象(比如被装饰类的example)。装饰器在python中使用非常广泛。这里有几个方面:(1)修改被装饰对象的属性或行为(2)处理函数对象执行的上下文,比如设置环境变量,添加日志等(3)处理重复For例如,有N个函数可能会抛出异常,但是我们并不关心这些异常,只要不把异常传递给调用者,这时候我们可以写一个catchall装饰器来作用于函数defcatchall可能会抛出异常(func):@functools.wraps(func)defwrapped(*args,**kwargs):try:returnfunc(*args,**kwargs)except:passreturnwrapped(4)框架代码,比如flask,bottle,等等,让使用框架的读者使用起来非常方便,也从本质上避免了重复代码。装饰器的精彩应用往往超出对应,经常在各种源码中看到各种神奇的用法,库壳这篇文章给出的例子也不错。参考pep0318:https://www.python.org/dev/peps/pep-0318/#syntax-alternativesPYTHON装饰器函数式编程:http://coolshell.cn/articles/11265.html