Python装饰器用法详解(附带实例)
装饰器是 Python 的重要特性之一,为了更好地描述装饰器,下面将通过实例一步一步地讲解。
首先实现一个简单的延时函数,并在程序中调用它:
假设此时想添加一个新功能:计算 timeTest() 函数的运行时间,则首先添加一个新的函数,在该函数中调用 timeTest() 函数,然后分别在调用前和调用后获取系统时间并求时间差,代码为:
很显然,这样的方式虽然实现了新的功能,但是更改了原程序的代码。有没有一种方式,既可以实现新的功能,又不需要更改原程序的代码呢?也就是只增不改。
答案是肯定的。Python 提供的装饰器可以满足这样的需求。
所谓装饰器,就是在不修改程序代码(包括函数的实现和调用方式)的基础上动态添加新的功能。就好比一个人打扮好(写好代码)准备出门了,突然想添加一个装饰品(新的功能),如想戴一顶帽子,那么最直接的方式就是在不改变当前打扮的情况下戴上帽子即可,而不是为了戴帽子还需要把已经穿好的衣服扒掉(修改写好的代码)。
使用装饰器可以重新实现计时功能:
基于以上程序,修改代码如下:
目前,针对被装饰函数带参数已经有了应对方法,对于装饰器本身带参数如何应对呢?下面通过实例进行介绍。
基于前文的实例,为装饰器添加一个 bool 变量,通过变量的真假判断是否调用计时功能:
装饰器的威力在于不需要修改原程序即可添加新的功能。比如,在Web的开发过程中,任何人都可以看网络新闻,如果想要评论,则需要先登录账号,此时就可以通过装饰器添加登录功能来修饰现有的程序。
首先实现一个简单的延时函数,并在程序中调用它:
import time def timeTest(): print('timeTest start') print('sleep 1 second...') time.sleep(1) print('timeTest end') timeTest()运行结果为:
timeTest start sleep 1 second... timeTest end定义一个时间测试函数,函数功能非常简单:延时 1 秒,在延时前和延时后先分别打印开始和结束,再在程序中调用该函数。
假设此时想添加一个新功能:计算 timeTest() 函数的运行时间,则首先添加一个新的函数,在该函数中调用 timeTest() 函数,然后分别在调用前和调用后获取系统时间并求时间差,代码为:
import time def calcTime(func): startTime = time.time() func() endTime = time.time() interval = endTime - startTime print('Time interval: %f secs' % interval) def timeTest(): print('timeTest start') print('sleep 1 second...') time.sleep(1) print('timeTest end') calcTime(timeTest)运行结果为:
timeTest start sleep 1 second... timeTest end Time interval: 1.001065 secs通过以上程序成功计算了 timeTest() 函数的运行时间。仔细观察程序可以发现,为了实现新功能,程序不仅新增了代码,还更改了原程序的代码:将 timeTest() 修改为 calcTime(timeTest)。
很显然,这样的方式虽然实现了新的功能,但是更改了原程序的代码。有没有一种方式,既可以实现新的功能,又不需要更改原程序的代码呢?也就是只增不改。
答案是肯定的。Python 提供的装饰器可以满足这样的需求。
所谓装饰器,就是在不修改程序代码(包括函数的实现和调用方式)的基础上动态添加新的功能。就好比一个人打扮好(写好代码)准备出门了,突然想添加一个装饰品(新的功能),如想戴一顶帽子,那么最直接的方式就是在不改变当前打扮的情况下戴上帽子即可,而不是为了戴帽子还需要把已经穿好的衣服扒掉(修改写好的代码)。
使用装饰器可以重新实现计时功能:
import time def calcTime(func): def wrapper(): startTime = time.time() func() endTime = time.time() interval = endTime - startTime print('Time interval: %f secs' % interval) return wrapper @calcTime def timeTest(): print('timeTest start') print('sleep 1 second...') time.sleep(1) print('timeTest end') timeTest()运行结果为:
timeTest start sleep 1 second... timeTest end Time interval: 1.001107 secs在代码中,黑体部分是新增代码,即只新增代码就能新增功能,并未修改原程序的代码。其中,@calcTime 中的 @ 是 Python 装饰器的语法糖。@calcTime 放在 timeTest() 函数的定义处,相当于执行了语句:timeTest=calcTime(timeTest)。
Python被装饰的函数带参数
在以上程序中,被装饰的函数 timeTest() 没带参数,如果带参数,则装饰器该如何构建呢?基于以上程序,修改代码如下:
import time def calcTime(func): def wrapper(num): startTime = time.time() func(num) endTime = time.time() interval = endTime - startTime print('Time interval: %f secs' % interval) return wrapper @calcTime def timeTest(num): print('timeTest start') print('sleep %d second...' % num) time.sleep(num) print('timeTest end') timeTest(1)运行结果为:
timeTest start sleep 1 second... timeTest end Time interval: 1.001097 secs可以看到,当被装饰的函数 timeTest() 带参数时,可将装饰器的内嵌函数修改为被装饰函数的形式。此时,timeTest(1) 相当于 calcTime(timeTest(1))。
Python带参数的装饰器
假设想要使用共同的装饰器来修饰多个不同的函数,不同的函数有形式不同的参数,则装饰器可以通过可变参数(*args,**kwargs)来实现内嵌函数。目前,针对被装饰函数带参数已经有了应对方法,对于装饰器本身带参数如何应对呢?下面通过实例进行介绍。
基于前文的实例,为装饰器添加一个 bool 变量,通过变量的真假判断是否调用计时功能:
import time def calcTime(flag=False): if flag: def _calcTime(func): def wrapper(*args, **kwargs): startTime = time.time() func(*args, **kwargs) endTime = time.time() interval = endTime - startTime print("Time interval: %f secs" % interval) return wrapper else: def _calcTime(func): return func return _calcTime @calcTime(False) def timeTest1(): print('timeTest1 start') print('sleep 1 second...') time.sleep(1) print('timeTest1 end') @calcTime(True) def timeTest2(num): print('timeTest2 start') print('sleep %d second...' % num) time.sleep(num) print('timeTest2 end') timeTest1() print() timeTest2(2)运行结果为:
timeTest1 start sleep 1 second... timeTest1 end timeTest2 start sleep 2 second... timeTest2 end Time interval: 2.007000 secs由程序和运行结果可知,将同一个装饰器 calcTime 用于两个不同的函数,分别是 timeTest1() 和 timeTest2(num),一个没带参数,一个带参数,通过装饰器的参数可以为装饰过程添加判断,@calcTime(True) 表示进行计时,@calcTime(False) 表示不会进行计时,程序运行结果也证明了这一点。
Python装饰器调用顺序
在以上程序中,使用一个装饰器修饰了一个函数,如果使用多个装饰器修饰同一个函数,则装饰器的调用顺序怎样呢?下面依然通过实例进行介绍。def dec1(func): print('dec1') def wrapper(): print('dec1_wrapper') func() return wrapper def dec2(func): print('dec2') def wrapper(): print('dec2_wrapper') func() return wrapper @dec1 @dec2 def testFunc(): print('testFunc') testFunc()运行结果为:
dec1 dec2 dec2_wrapper dec1_wrapper testFunc由运行结果可知,装饰器的调用顺序与语法糖 @ 的声明顺序相反。在程序中,testFunc 相当于 dec1(dec2(testFunc))。
装饰器的威力在于不需要修改原程序即可添加新的功能。比如,在Web的开发过程中,任何人都可以看网络新闻,如果想要评论,则需要先登录账号,此时就可以通过装饰器添加登录功能来修饰现有的程序。