装饰器的应用实例
1 import time 2 def timeit(fn): 3 start = time.time() 4 fn() 5 print(time.time() - start) 6 7 def sleep(): 8 time.sleep(3)
上边代码定义了两个函数,timeit函数能够模拟计算出在执行fn函数所花费的时间
1 timeit(sleep) 2 3.003638505935669
这样来计算一个函数的执行时间是有缺陷的,sleep
函数必须是一个接收参数的函数,那怎样才能让sleep
能接收参数呢?做如下改进:
1 def timeit_1(fn): 2 def wrap(x): 3 start = time.time() 4 fn(x) 5 print(time.time() - start) 6 return wrap 7 8 9 def sleep_1(x): 10 time.sleep(x)
1 timeit_1(sleep_1)(3) 2 3.0035746097564697
这样timeit_1
函数通过wrap
函数进行一次包装后就可以让sleep
函数接收一个参数,但如果sleep
函数所接收的参数个数是不确定的呢?
这个可以采用python中的可变参数来解决,如下:
1 def timeit_2(fn): 2 def wrap(*args,**kwargs): 3 start = time.time() 4 fn(*args,**kwargs) 5 print(time.time() - start) 6 return wrap
这样对于sleep
这个函数所需要接收的参数个数就没有限制了,调用方法与上边的不变
1 timeit_2(sleep_1)(3) 2 3.003852605819702
其实这里的timeit_2
就是一个装饰器
,在python中有一个语法糖来表示,如果在执行一个函数时,比如上边的sleep_1
函数想在其执行前后增加一些语句操作,比如上边的start = time.time()
和print(time.time() - start)
,那在定义sleep_1
函数时就可以加上一个装饰器来装饰此函数,这样定义的函数有其独特的语法,在定义函数时在其上边用一个@
符号加上装饰器函数的名称即可
1 @timeit_2 2 def sleep_2(x): 3 time.sleep(x)
如上定义后的sleep_2
的函数,我们在调用时就不需要再去调用timeit_2
这个函数了,直接调用sleep_2
函数即可,如下:
1 sleep_2(3) 2 3.0038673877716064
调用sleep_2(3)
时,函数的执行流程:
首先把sleep_2
函数作为参数传递到timeit_2
这个装饰器函数中执行,返回一个wrap
函数对象
再把调用sleep_2(3)
函数时的参数3
传递到wrap
函数参数中进行函数调用,实质就是解释器会转换成timeit_2(sleep_2)(3)
的方式来调用,
但是在已经使用@timeit_2
语法来装饰函数sleep_2
的场景下不能再使用timeit_2(sleep_2)(3)
来调用函数,因为这样wrap
函数会被执行两次。
总结一下:
装饰器的本质就是一个函数,此函数接收一个函数作为参数,返回一个函数,通常,返回的这个函数,是对传入的函数执行进行前后增加了一些语句,所以叫做装饰器。