函数装饰器Python中的装饰器(Decorators),主要作用是修改函数的功能,修改的前提是不改变原有函数代码,装饰器将返回一个函数对象,因此在某些地方装饰器被称为“函数的函数”。还有一种设计模式叫做“装饰者模式”,后续课程会讲到。在调用装饰器的时候,使用@,这是Python提供的一种编程语法糖,使用后会让你的代码看起来更Pythonic。装饰器的基本使用在学习装饰器的时候,最常见的情况就是统计一个函数的运行时间,接下来分享给大家。计算函数运行时间:importtimedeffun():i=0whilei<1000:i+=1deffun1():i=0whilei<10000:i+=1s_time=time.perf_counter()fun()e_time=time.perf_counter()print(f"函数{fun.__name__}的运行时间为:{e_time-s_time}")如果要在每个对应关系中加上调用时间,工作量巨大,需要repeat修改函数内部的代码,或者修改函数调用处的代码。在这种需求下,装饰器语法出现了。我们先来看看第一种修改方法。该方法没有添加装饰器,而是写了一个通用函数,利用了Python中函数可以作为参数的特点,完成了代码的可重用性。importtimedeffun():i=0whilei<1000:i+=1deffun1():i=0whilei<10000:i+=1defgo(fun):s_time=time.perf_counter()fun()e_time=time.perf_counter()print(f"Function{fun.__name__}runningtimeis:{e_time-s_time}")if__name__=="__main__":go(fun1)接下来这个技巧扩展到PythonWrapper语法中的装饰,具体修改如下:importtimedefgo(func):#这里的wrapper函数名可以是任意名字defwrapper():s_time=time.perf_counter()func()e_time=time.perf_counter()print(f"函数{func.__name__}运行时间:{e_time-s_time}")returnwrapper@godeffunc():i=0whilei<1000:i+=1@godeffunc1():i=0whilei<10000:i+=1if__name__=='__main__':func()上面代码中,注意gofunction部分。它的参数func是一个函数,返回值是一个内部函数。执行代码后,相当于注入了原函数代码来计算时间。代码调用部分,不作任何改动,函数func的功能更多(计算运行时间的功能)。装饰器函数在不修改原函数代码的情况下成功扩展了原函数的功能。学完这个案例,你对装饰器有了初步的了解。带参数装饰函数直接看代码学习如何带参数装饰函数:importtimedefgo(func):defwrapper(x,y):s_time=time.perf_counter()func(x,y)e_time=time。perf_counter()print(f"Function{func.__name__}运行时间为:{e_time-s_time}")returnwrapper@godeffunc(x,y):i=0whilei<1000:i+=1print(f"x={x},y={y}")if__name__=='__main__':func(33,55)看晕了,我给你把重要的参数传递过程标记一下。还有一种情况是装饰器本身有参数,比如下面的代码:deflog(text):defdecorator(func):defwrapper(x):print('%s%s():'%(text,func.__name__))func(x)returnwrapperreturndecorator@log('execute')defmy_fun(x):print(f"我是my_fun函数,我的参数{x}")my_fun(123)上面的代码在在写装饰器函数的时候,在装饰器函数外面嵌套了一层函数,最终的代码运行顺序如下:得出结论,使用带参数的装饰器就是在装饰器外面包装一个函数,用这个函数接收参数,返回一个装饰器函数。另外需要注意的是,装饰器只能接收一个参数,而且必须是函数类型。多个装饰器先复制以下代码,然后研究学习。importtimedefgo(func):defwrapper(x,y):s_time=time.perf_counter()func(x,y)e_time=time.perf_counter()print(f"function{func.__name__}运行时间是:{e_time-s_time}")returnwrapperdefgogo(func):defwrapper(x,y):print("Iamthesecondwrapper")returnwrapper@go@gogodeffunc(x,y):i=0whilei<1000:i+=1print(f"x={x},y={y}")if__name__=='__main__':func(33,55)代码运行后输出为:Iamthefirst两个装饰器函数wrapper的运行时间为:0.0034401339999999975多个装饰器的使用虽然很简单,但是问题也随之而来,print(f"x={x},y={y}")这段代码的结果是lost是的,这里就是多个装饰器的执行顺序问题。先解释一下装饰器的装饰顺序。importtimedefd1(func):defwrapper1():print("装饰器1开始装饰")func()print("装饰器1结束装饰")returnwrapper1defd2(func):defwrapper2():print("装饰器2startstodecorate")func()print("Decorator2endsdecoration")returnwrapper2@d1@d2deffunc():print("Thedecoratedfunction")if__name__=='__main__':func()运行的结果上面的代码是:Decorator1开始装饰Decorator2开始装饰被装饰函数Decorator2结束装饰Decorator1结束装饰你可以看到一个非常对称的输出,同时证明被装饰函数在最里面层,转换成函数调用的代码如下:d1(d2(func))这部分需要注意的是装饰器的外层函数和内层函数之间的语句没有装饰在target函数,但是在加载时进行了额外的操作。装饰函数时,会运行外层函数和内层函数之间的代码。测试效果如下:importtimedefd1(func):print("我是d1的内外函数之间的代码")defwrapper1():print("装饰器1开始装饰")func()print("装饰器1结束装饰")returnwrapper1defd2(func):print("我是d2的内外函数之间的代码")defwrapper2():print("装饰器2开始装饰")func()print("Decorator2endsdecoration")returnwrapper2@d1@d2deffunc():print("decoratedfunction")运行后,可以看到输出如下:Iamtheinnerandouterfunctionsofd2I是d1的内部函数和外部函数之间的代码d2函数在d1函数之前运行。接下来回顾一下装饰器的概念:被装饰函数的名字会作为参数传递给被装饰函数。被装饰函数执行完自己的内部代码后,将自己的返回值赋值给被装饰函数。这样,上面代码的运行过程就是这样的。d1(d2(func))执行d2(func)后,原函数名func会指向wrapper2函数。执行d1(wrapper2)函数后,wrapper2的函数名会指向wrapper1。因此,当最后一个func被调用时,就意味着代码已经切换到了下面的内容。#第一步defwrapper2():print("装饰器2开始装饰")print("装饰函数")print("装饰器2结束装饰")#第二步print("装饰器1开始装饰")wrapper2()print("decorator1endsdecoration")#第三步defwrapper1():print("decorator1startsdecoration")print("decorator2startsdecoration")print("decoratedFunction")print("Decorator2endsdecoration")print("Decorator1endsdecoration")上面第三步之后的代码运行起来和我们的代码输出完全一样。再回到本节开头的案例,输出数据为什么会丢失。importtimedefgo(func):defwrapper(x,y):s_time=time.perf_counter()func(x,y)e_time=time.perf_counter()print(f"function{func.__name__}运行时间是:{e_time-s_time}")returnwrapperdefgogo(func):defwrapper(x,y):print("Iamthesecondwrapper")returnwrapper@go@gogodeffunc(x,y):i=0whilei<1000:i+=1print(f"x={x},y={y}")if__name__=='__main__':func(33,55)执行装饰器代码decoration后,调用func(33,55)已经切换到go(gogo(func)),运行gogo(func)的代码转换为如下:defwrapper(x,y):print("Iamtheseconddecorator")runninggo(wrapper),代码转换为:s_time=time.perf_counter()print("我是第二个装饰器")e_time=time.perf_counter()print(f"函数{func.__name__}运行时间为:{e_time-s_time}")此时你会发现在运行过程中参数丢失了。functools.wraps使用装饰器可以大大提高代码的复用性,但缺点是丢失了原函数的元信息,比如函数的__doc__和__name__:#decoratordeflogged(func):deflogging(*args,**kwargs):print(func.__name__)print(func.__doc__)func(*args,**kwargs)returnlogging#Function@loggeddeff(x):"""函数文档,描述"""returnx*xprint(f.__name__)#Outputloggingprint(f.__doc__)#OutputNone解决方法很简单,importfromfunctoolsimportwraps,修改代码如下:fromfunctoolsimportwraps#Decoratordeflogged(func):@包装(func)deflogging(*args,**kwargs):print(func.__name__)print(func.__doc__)func(*args,**kwargs)returnlogging#function@loggeddeff(x):"""functionDocumentation,description"""returnx*xprint(f.__name__)#输出fprint(f.__doc__)#输出函数文档,说明基于类的装饰器一般都是“函数装饰器”在实际编码中,“类装饰器”出现的频率要低得多。基于类的装饰器与函数的基本用法一致。先看一段代码:classH1(object):def__init__(self,func):self.func=funcdef__call__(self,*args,**kwargs):return''+self.func(*args,**kwargs)+''@H1deftext(name):returnf'text{name}'s=text('class')print(s)H1类有两个方法:__init__:接收一个函数作为参数,这是要装饰的功能;__call__:让类对象可调用,类似于函数调用,触发点在被修饰函数被调用时触发。最后,附录中有一篇写得很好的博客,可以借鉴一下。类装饰器的细节这里就不展开了,后面滚雪球相关项目的实际操作中再说。装饰器作为一个类和一个类的装饰器的细节是不同的。上面说了,装饰器是一个类。你可以考虑如何给一个类添加装饰器。内置装饰器常见的内置装饰器有@property、@staticmethod、@classmethod。这部分的内容在面向对象的详细部分进行了讲解,本文只做简单的说明。@property将类中的方法作为属性使用,必须有返回值,相当于getter。如果没有定义@func.setter修饰方法,则为只读属性。@staticmethod静态方法不需要表示自己对象的self和自己类的cls参数,就像使用函数一样。@classmethod类方法不需要self参数,但是第一个参数需要是代表自己类的cls参数。这篇博客的总结是关于Python装饰器的。网上的文章太多了。不难学。真正的困难是在项目中正确地应用它们。希望这篇博客能帮助大家了解装饰器。其他内容也可以参考官方手册
