Python是一种非常适合初学者的语言。但是,它也有很多更难掌握的高级特性,比如装饰器。许多初学者一直对装饰器及其工作原理一无所知。在本文中,我们将介绍装饰器的来龙去脉。在Python中,函数是一种非常灵活的构造,我们可以将其分配给变量,作为参数传递给另一个函数,或者作为函数的输出。装饰器本质上是一个函数,它允许其他函数在不修改的情况下添加一些功能。这就是“装饰”的意义。这个“装饰”本身就代表了一种功能。如果你用它来修改不同的功能,你会把这个功能添加到这些功能中。一般来说,我们可以使用装饰器提供的@语法糖来装饰其他函数或对象。如下所示,我们使用@dec装饰器来装饰函数func():@decdeffunc():pass理解装饰器的最好方式就是理解它们解决的是什么问题。彰显其优雅与力量。设置问题为了理解装饰器的用途,让我们看一个简单的例子。假设你有一个简单的加法函数dec.py,第二个参数的默认值为10:#dec.pydefadd(x,y=10):returnx+y让我们仔细看看这个加法函数:>>>add(10,20)30>>>add>>>add.__name__'add'>>>add.__module__'__main__'>>>add.__defaults__#`add`的默认值function(10,)>>>add.__code__.co_varnames#`add`function('x','y')的变量名我们不需要理解这些是什么,只需要记住每一个函数就是一个对象,并且它们具有各种属性和方法。也可以通过inspect模块查看add()函数的源码:>>>frominspectimportgetsource>>>print(getsource(add))defadd(x,y=10):returnx+y那里有几种使用加法函数的方法,比如你用一些操作来测试函数:#dec.pyfromtimeimporttimedefadd(x,y=10):returnx+yprint('add(10)',add(10))print('add(20,30)',add(20,30))print('add("a","b")',add("a","b"))输出:iadd(10)20add(20,30)50add("a","b")ab如果想知道每次操作的时间,可以调用时间模块:#dec.pyfromtimeimporttimedefadd(x,y=10):returnx+ybefore=time()print('add(10)',add(10))after=time()print('timetaken:',after-before)before=time()print('add(20,30)',add(20,30))after=time()print('所用时间:',after-before)before=time()print('add("a","b")',add("a","b"))after=time()print('timetaken:',after-before)Output:add(10)20timetaken:6.699562072753906e-05add(20,30)50次拍摄:6。9141387939453125e-06add("a","b")abtimetaken:6.9141387939453125e-06现在,作为程序员的你是不是手痒了,毕竟我们不喜欢一直复制粘贴相同的代码。目前的代码可读性不是很好。如果你想改变一些东西,你必须修改所有出现的地方。Python一定有更好的方法。我们可以直接在add函数中获取运行时间,如下:#dec.pyfromtimeimporttimedefadd(x,y=10):before=time()rv=x+yafter=time()print('timetaken:',之后-之前)returnrvprint('add(10)',add(10))print('add(20,30)',add(20,30))print('add("a","b")',add("a","b"))这个方法肯定比上一个好。但是如果你有另一个功能,那么这似乎很不方便。当我们有多个函数时:#dec.pyfromtimeimporttimedefadd(x,y=10):before=time()rv=x+yafter=time()print('timetaken:',after-before)returnrvdefsub(x,y=10):returnx-yprint('add(10)',add(10))print('add(20,30)',add(20,30))print('添加("a","b")',add("a","b"))print('sub(10)',sub(10))print('sub(20,30)',sub(20,30))由于add和sub都是函数,我们可以用它来写一个定时器函数。我们希望定时器可以计算一个函数的运行时间:deftimer(func,x,y=10):before=time()rv=func(x,y)after=time()print('timetaken:',after-before)returnrv这很好,但是我们必须用这样的计时器函数包装一个不同的函数:print('add(10)',timer(add,10)))现在默认值还是10吗?不必要。那么如何做得更好呢?这里有一个想法:创建一个新的计时器函数来包装其他函数并返回包装后的函数:deftimer(func):deff(x,y=10):before=time()rv=func(x,y)after=time()print('timetaken:',after-before)returnrvreturnf现在,您只需用计时器包装add和sub函数:add=timer(add)就这样!这是完整的代码:#dec.pyfromtimeimporttimedeftimer(func):deff(x,y=10):before=time()rv=func(x,y)after=time()print('timetaken:',after-before)returnrvreturnfdefadd(x,y=10):returnx+yadd=timer(add)defsub(x,y=10):返回x-ysub=timer(sub)print('add(10)',add(10))print('add(20,30)',add(20,30))print('add("a","b")',add("a","b"))print('sub(10)',sub(10))print('sub(20,30)',sub(20,30))输出:所用时间:0.0add(10)20time拍摄时间:9.5367431640625e-07add(20,30)50拍摄时间:0.0add("a","b")大约拍摄时间:9.5367431640625e-07sub(10)0拍摄时间:9.5367431640625e-07sub(20,30)-10here我们去总结一下这个过程:我们有一个函数(比如添加函数)并用一个动作(比如计时)包装该函数。包装的结果是一个实现了一些新功能的新函数。当然,默认值有点问题,我们稍后会修复。Decorator现在,上面的方案很接近decorator的思想,用通用的行为包装一个特定的功能,这种模式就是decorator在做的事情。使用装饰器后的代码是:defadd(x,y=10):returnx+yadd=timer(add)你这样写:@timerdefadd(x,y=10):returnx+y他们的功能是一样的是的,这就是Python装饰器的用途。它实现了一个类似add=timer(add)的功能,只是装饰器把语法放在了函数上,语法更简单:@timer。#dec.pyfromtimeimporttimedeftimer(func):deff(x,y=10):before=time()rv=func(x,y)after=time()print('所用时间:',after-before)returnrvreturnf@timerdefadd(x,y=10):returnx+y@timerdefsub(x,y=10):returnx-yprint('add(10)',add(10))打印('add(20,30)',add(20,30))print('add("a","b")',add("a","b"))print('sub(10)',sub(10))print('sub(20,30)',sub(20,30))参数和关键字参数现在,还剩下一个小问题。在定时器函数中,我们硬编码了参数x和y,即指定y的默认值为10。有一种方法可以将参数和关键字参数传递给函数,即*args和**kwargs。形参是函数的标准参数(本例中x是实参),关键字实参是已经有默认值的形参(本例中y=10)。代码如下:#dec.pyfromtimeimporttimedeftimer(func):deff(*args,**kwargs):before=time()rv=func(*args,**kwargs)after=time()print('timetaken:',after-before)returnrvreturnf@timerdefadd(x,y=10):returnx+y@timerdefsub(x,y=10):returnx-yprint('添加(10)',add(10))print('add(20,30)',add(20,30))print('add("a","b")',add("a","b"))print('sub(10)',sub(10))print('sub(20,30)',sub(20,30))现在,计时器函数可以处理任何函数、任何参数和任何默认值值设置,因为它只是将那些参数传递给函数。高阶装饰器您可能想知道:如果我们可以将一个函数包裹在另一个函数周围以添加有用的行为,我们是否可以更进一步?我们是否将一个函数包装在另一个函数周围,而另一个函数又被另一个函数包装?能!事实上,函数的深度可以随心所欲。比如你要写一个装饰器让某个函数执行n次。如下:defntimes(n):definner(f):defwrapper(*args,**kwargs):for_inrange(n):rv=f(*args,**kwargs)returnrvreturnwrapperreturninner那么就可以用上面的函数包裹另外一个函数,比如上一篇文章中的add函数:@ntimes(3)defadd(x,y):print(x+y)returnx+y输出的语句显示代码执行了3次。