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的开发过程中,任何人都可以看网络新闻,如果想要评论,则需要先登录账号,此时就可以通过装饰器添加登录功能来修饰现有的程序。
ICP备案:
公安联网备案: