python装饰器系列(三)

装饰器的应用实例

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函数会被执行两次。

总结一下:

装饰器的本质就是一个函数,此函数接收一个函数作为参数,返回一个函数,通常,返回的这个函数,是对传入的函数执行进行前后增加了一些语句,所以叫做装饰器。

原文地址:https://www.cnblogs.com/tianshug/p/10921830.html