当前位置: 首页 > 后端技术 > Python

Python装饰器_1

时间:2023-03-25 23:22:31 Python

介绍装饰器。如果你想在一个函数执行前后执行一些其他的代码,比如打印一点日志来输出这个函数的调用,怎么办?#!/usr/bin/envpython#coding=utf-8deflogger(fn):#函数作为参数,即fn可以是任意参数defwrap(*args,**kwargs):#可变参数argsandkwargsprint('call{}'.format(fn.__name__))ret=fn(*args,**kwargs)#调用函数时的参数解构print('{}called'.format(fn.__name__))returnret#返回函数的返回值returnwrapdefadd(x,y):returnx+ylogger_add=logger(add)print(logger_add.__name__)print(logger_add)ret=logger_add(3,5)print(ret)#输出结果:wrap.wrapat0x7fba35f4fe18>calladdaddcalled8这个效果也可以通过下面的方式实现@loggerdefadd(x,y):returnx+是ret=add(3,5)print(ret)#Output:calladdaddcalled8这是Python装饰器的简单使用什么是装饰器?装饰器是用于软件设计模式的名称。装饰器可以动态更改函数、方法或类的功能,而无需直接对其进行子类化或更改被装饰函数的源代码。Python装饰器是对Python语法的一种特殊更改,它使我们能够更轻松地修改函数、方法和类。当我们这样写代码的时候:@loggerdefadd(x,y):...和单独做下面的步骤是一样的:defadd(x,y):...logger_add=logger(add)decorator的内部代码一般新建一个函数,使用*args和**kwargs接受任意参数,上面代码中的wrap()函数就是这样的。在这个函数内部,我们需要调用原始的输入函数(也就是包装函数,是装饰器的输入参数)并返回它的结果。但是你也可以添加任何你想要的代码,比如输出上面代码中的函数调用,还可以添加定时处理等等。这个新创建的包装函数作为装饰器的结果返回,替换了原来的函数。所以在Python中,装饰器的参数是函数,返回值是函数的函数。装饰器示例:定时处理编写一个装饰器计算函数的执行时间**kwargs)end=time.time()print(fn.__name__,end-start)returnretreturnwrap如果你想计时add函数:@timethisdefadd(x,y):returnx+yret=add(3,5)print(ret)#Outputresultadd1.9073486328125e-068如果要定时sleep函数:@timethisdefsleep(x):time.sleep(x)sleep(3)#Outputresultsleep3.003262519836426保存decoratedfunctionMetainformation一个函数的元信息是什么,比如装饰器的名字,装饰器的doc等等。我们可以使用dir函数列出函数的所有元信息:dir(sleep),输出如下['__annotations__','__call__','__class__','__closure__','__code__','__defaults__','__delattr__','__dict__','__dir__','__doc__','__eq__','__format__','__ge__','__get__','__getattribute__','__globals__','__gt__','__hash__','__init__','__kwdefaults__','__le__','__lt__','__module__','__name__','__ne__','__new__','__qualname__','__reduce__','__reduce_ex__','__repr__','__setattr__','__sizeof__','__str__','__subclasshook__']可以看到元信息很多,我们比较常用的是__name__和__doc__这两个属性,__doc__属性也是文档信息函数,可以通过帮助函数查看为什么要保存被装饰函数的元信息,重写装饰器的应用1:定时处理中的sleep函数如下:@timeitdefsleep(x):'''这个函数是sleep.'''time.sleep(x)sleep(3)print(sleep.__name__)print(sleep.__doc__)上面代码输出结果如下:3.0032713413238525wrapNone可以发现sleep函数的__name__是wrap,不是sleep,__doc__属性是空的,不是sleep函数的docstring。也就是说,装饰器装饰的函数的元信息发生了变化。这时候,如果程序需要函数的元信息,那就有问题了。如何保存被装饰函数的元信息方案一:手动赋值被装饰函数的元信息以__name__和__doc__两个属性为例importtimedeftimeit(fn):defwrap(*args,**kwargs):start=time.time()ret=fn(*args,**kwargs)end=time.time()print(end-start)returnretwrap.__doc__=fn.__doc__#手动分配__doc__信息wrap.__name__=fn.__name__#手动赋值__name__信息returnwrap@timeitdefsleep(x):'''这个函数是sleep.'''time.sleep(x)if__name__=="__main__":sleep(3)#print(dir(sleep))print(sleep.__name__)print(sleep.__doc__)输出结果如下:3.004547119140625sleep这个函数是sleep。可以发现__name__和__doc__这两个属性确实赋值成功了。我们可以把元信息赋值的过程重写成一个函数,如下__doc__deftimeit(fn):defwrap(*args,**kwargs):start=time.time()ret=fn(*args,**kwargs)end=time.time()print(end-start)返回retcopy_properties(fn,wrap)#调用copy_properties函数修改元数据returnwrap@timeitdefsleep(x):'''这个函数是sleep.'''time.sleep(x)if__name__=="__main__":sleep(3)#print(Dir(sleep))print(sleep.__name__)print(sleep.__doc__)这样修改后也能解决问题。继续修改copy_properties函数,让copy_properties可以返回一个函数defcopy_properties(src):def_copy(dst):#一个内置的_copy函数,方便返回dst.__name__=src.__name__dst.__doc__=src.__doc__return_copydeftimeit(fn):defwrap(*args,**kwargs):start=time.time()ret=fn(*args,**kwargs)end=time.time()print(end-start)返回retcopy_properties(fn)(wrap)#调用copy_properties函数returnwrap也会有问题。如果继续修改copy_properties函数,使_copy函数为装饰器,传入dst并返回dst,修改如下:defcopy_properties(src):#首先修复dst,传入srcdef_copy(dst):#传入dstdst.__name__=src.__name__dst.__doc__=src.__doc__returndst#returndstreturn_copy#返回一个装饰器deftimeit(fn):@copy_properties(fn)#usemethodwithparameterdecoratordefwrap(*args,**kwargs):start=time.time()ret=fn(*args,**kwargs)end=time.time()print(end-start)returnretreturnwrapcopy_properties这里返回一个带参数的装饰器,所以wrap函数可以直接根据装饰器的用法进行装饰。修改copy_properties函数的过程称为函数柯里化。方案二:使用functools库的@wraps装饰器functools库的@wraps装饰器本质上是copy_properties函数的高级版本:它包含了更多的函数元信息。首先查看wrap装饰器的帮助信息:importfunctoolshelp(functools.wraps)wrap装饰器函数原型为:wraps(wrapped,assigned=('module','name','qualname','doc','annotations'),updated=('dict',))所以这个装饰器会复制元信息如模块,但不是所有的元信息,并且会更新字典。使用示例如下:importtimeimportfunctoolsdeftimeit(fn):@functools.wraps(fn)#wraps装饰器的使用defwrap(*args,**kwargs):start=time.time()ret=fn(*args,**kwargs)end=time.time()print(end-start)returnrereturnwrapdefsleep(x):time.sleep(x)print(sleep.__name__)print(sleep.__doc__)写一个装饰器parameters如果是上面提到的timeit装饰器,我们需要输出执行时间超过几秒(比如一秒)的函数的名称和执行时间,那么我们需要给装饰器传递一个参数s,表示传入的时间间隔,默认为1s。我们可以在写好的装饰器外面包裹一个函数timeitS,时间间隔s作为这个函数的参数传入,对内部函数可见,然后这个函数返回写好的装饰器。导入时间导入functoolsdeftimeitS(s):deftimeit(fn):@functools.wraps(fn)defwrap(*args,**kwargs):start=time.time()ret=fn(*args,**kwargs)end=time.time()ifend-start>s:print('call{}takes{}s'.format(fn.__name__,end-start))else:print('call{}takes{}sless比{}'.format(fn.__name__,end-start,s))returnrereturnreturnwrapreturntimeit@timeitS(2)defsleep(x):time.sleep(x)sleep(3)sleep(1)output结果如下:callsleeptakes3.001342535018921scallsleeptakes1.000471830368042s不到2所以带参数的装饰器我们可以理解为:带参数的装饰器是一个函数,这个函数返回一个不带参数的装饰器记得帮帮我喜欢它!对计算机各个方向的视频课程和电子书,从入门、进阶、实用进行了认真梳理,并按照目录进行合理分类。你总能找到你需要的学习资料。你在等什么?立即关注并下载!!!念念不忘,必有回响,朋友们,请点个赞,万分感谢。我是职场亮哥,四年工作经验的YY高级软件工程师,拒绝当领导的斜杠程序员。听我说,我进步很大。如果有幸帮到你,请给我一个【点赞】,给我一个关注,如能评论鼓励,将不胜感激。职场凉阁文章列表:更多文章我的所有文章和回答均与版权保护平台合作,版权归职场凉阁所有。未经授权转载必究!