Python允许使用装饰器对函数进行装饰,这样编写函数时就可以专注于功能的实现,而装饰器可以帮助函数实现一些通用的功能,在函数调用前运行写预备代码或函数调用后执行些清理工作.比如:插入日志,检测性能和事务管理.
装饰器实质上还是一个函数,用来包装别的函数的函数.包装后返回一个装饰后的函数对象,该函数对象将更新原函数对象,程序将不再能访问原原始函数对象.
装饰器使用过程:
- 定义装饰器函数,假设装饰器函数的名称为decl;
- 使用装饰器,即@decol
1 无参数装饰器
无参数装饰器格式:
@decol def fun1(): ...
以上等价于fun1 = decol1(fun1);
Python也允许使用多个装饰器修饰一个函数,如:
@decol1 @decol2 def fun1(): ...
以上代码等价于fun1 = decol1(decol2(fun1)).
一下我们通过例子看无参数装饰器的使用:
1 import time 2 3 # 定义一个计时函数,其参数为一个函数,用于接受被装饰的函数 4 def time_stt(func): 5 # 定义一个内嵌的包装函数,记录函数开始时间和结束时间 6 def wrapper(): 7 start = time.time() 8 func() 9 usetime = time.time() - start 10 print("执行函数%s用时%ds"%(func.__name__,usetime)) 11 return wrapper 12 13 # 调用装饰器 14 @time_stt 15 def test(): 16 time.sleep(4) 17 18 # 测试结果 19 test()
以上定义的装饰器函数time_stt可以装饰所有没有参数的函数,统计其用时.但如果用其装饰有参数的函数时就会出错:
1 import time 2 3 # 定义一个计时函数,其参数为一个函数,用于接受被装饰的函数 4 def time_stt(func): 5 # 定义一个内嵌的包装函数,记录函数开始时间和结束时间 6 def wrapper(): 7 start = time.time() 8 func() 9 usetime = time.time() - start 10 print("执行函数%s用时%ds"%(func.__name__,usetime)) 11 return wrapper 12 13 # 调用装饰器 14 @time_stt 15 def test(n): 16 time.sleep(n) 17 18 # 测试结果 19 test(4) 20 21 ### TypeError: wrapper() takes 0 positional arguments but 1 was given
这是因为调用使用decol装饰test(4)函数时,实质上是执行wrapper(4),然而以上我们定义的wrapper是一个无参函数.如何改正这个错误,并让装饰器函数decol能够装饰有任意多个任意参数的函数呢?这需要用到可变数量参数.
1 import time 2 3 # 定义一个计时函数,其参数为一个函数,用于接受被装饰的函数 4 def time_stt(func): 5 # 定义一个内嵌的包装函数,记录函数开始时间和结束时间 6 def wrapper(*t,**d): 7 start = time.time() 8 func(*t,**d) 9 usetime = time.time() - start 10 print("执行函数%s用时%ds"%(func.__name__,usetime)) 11 return wrapper 12 13 # 调用装饰器 14 @time_stt 15 def test(n): 16 time.sleep(n) 17 18 # 测试结果 19 test(4)
2 带参数装饰器
@deco(deco_arg) def fun(): ...
以上等价于fun = deco(deco_arg)(fun).
和无参数装饰器比较可知,带参数装饰器起装饰作用的是deco(deco_arg),即deco函数返回值必须是一个函数.
1 import time 2 3 # 采用带参数的装饰器函数 4 # 根据装饰器的参数选择是记录函数开始调用的时间韩式函数结束的时间 5 def deco(sel): 6 def startdec(func): 7 def r(*t,**d): 8 print(func.__name__,"开始调用的时间为:",time.ctime()) 9 func(*t,**d) 10 return r 11 def enddec(func): 12 def r(*t,**d): 13 func(*t,**d) 14 print(func.__name__,"结束的时间为:",time.ctime()) 15 return r 16 17 try: 18 return {'start':startdec,'end':enddec}[sel] 19 except KeyError as e: 20 raise(ValueError(e),'必须是start或end') 21 22 @deco('end') 23 def sp(seq): 24 for n in seq: 25 print(n) 26 27 sp([i for i in range(6)])