很多人学到python的装饰器之后,感受到泰山压顶般的困难,似乎这是一座不可逾越的高山。本文尝试用最简单明了的语言为你解释什么是装饰器。
我们写了一些函数,实现了某个功能,最后执行代码时,发现代码执行速度很慢,于是,你想获得函数执行时长,找出比较慢的那一个,这就是我们讲解装饰的技术背景,下面是试验函数,为了模拟函数执行时长,我在函数里使用了sleep
import time
def slow_func1():
time.sleep(0.1) # 这个很快
def slow_func2():
time.sleep(3) # 假装很慢
def run():
for i in range(20):
slow_func2()
slow_func1()
run() # 执行程序
现在,你不知道这两个函数各自执行时长,脚本启动后,执行run方法,速度不理想,我们需要一个方法来知晓程序慢在哪里。
想要获取函数执行时长,这还不容易,函数执行之前,获取当前时间,函数执行结束以后,再获取当前时间,时间差就是函数的执行时长
t1 = time.time()
slow_func1()
t2 = time.time()
print("slow_func1执行时长是: ", t2-t1, '秒')
t1 = time.time()
slow_func2()
t2 = time.time()
print("slow_func2执行时长是: ", t2-t1, '秒')
程序输出结果
slow_func1执行时长是: 0.10361218452453613 秒
slow_func2执行时长是: 3.0028281211853027 秒
very good,这种方法可以解决我们所面临的问题,但是,它有缺陷,如果有20个函数都需要获取执行时长,该怎么办?一个函数就需要4行代码,20个函数就是80行代码,我是搞技术的,不是来被技术搞的,不行,我需要换一个思路。
测量函数执行时长的代码,看起来长的都一样啊,我编写一个函数就不可以了么
def func_cost(func):
t1 = time.time()
func()
t2 = time.time()
print("{name}执行时长是: ".format(name=func.__name__), t2-t1, '秒')
func_cost(slow_func1)
func_cost(slow_func2)
我编写一个func_cost函数,它有一个参数,我将slow_func1,slow_func2作为参数传入到func_cost中,在func_cost函数中执行并计算时长,就可以达到相同的效果,程序输出结果是
slow_func1执行时长是: 0.10442304611206055 秒
slow_func2执行时长是: 3.0001602172851562 秒
函数也能作为参数是理解这一段代码的关键,如果你能理解这一段代码,那么恭喜你,你离掌握装饰器已经不远了,目前的这个方法可以解决我们的问题,函数func_cost用了5行代码,假设有20个函数需要测量,那么只需要再写20行调用func_cost函数的语句就可以了,但是,它也是有缺陷的。
假设,slow_func1函数在整个程序的执行期间要执行100次,我想知道每一次执行的时长,该怎么办?为了方便,我在定义slow_func1时没有定义参数,而实际工作中,一个有参数的函数由于参数的不同,执行时长也可能会不同。
现在这个解决办法,不能模拟出程序真实的运行过程,只能用func_cost函数把每一个待测量的函数执行一次,我们需要一种方法,可以在程序真实的运行过程中测量函数执行时长。
先上代码,再做解释
def func_cost(func):
def wapper():
t1 = time.time()
func()
t2 = time.time()
print("{name}执行时长是: ".format(name=func.__name__), t2-t1, '秒')
return wapper
slow_func1 = func_cost(slow_func1)
slow_func2 = func_cost(slow_func2)
run() # 执行程序
这一次的func_cost函数更加复杂了一点,在func_cost内部,我定义了一个函数wapper, 先不关心wapper函数里面的东西,明确一点,函数func_cost的返回值是wapper,不必大惊小怪,函数既能做参数,也能做返回值。
func_cost(slow_func1) 在执行时,返回的是一个名为wapper的函数,在函数wapper内部,会测量slow_func1的执行时长,wapper函数最终赋值给变量slow_func1, run函数执行时,run函数里的slow_func1不再是之前用def定义的那个slow_func1,而是使用func_cost函数得到的wapper。
好吧,不论怎样言简意赅,装饰器都确实很难轻易说清楚,这就是你对装饰器理解的最后一道山岗,胜利就在前方。这个方法解决了前面出现过的所有问题,但是,它还是有缺陷,为了测量函数的执行时长,这个方法实际上是用func_cost创建出一个新的函数然后替代之前用def定义的函数,测量结束后,再删掉这些代码么,有没有什么更加简便的写法?
先上代码,再做解释
import time
def func_cost(func):
def wapper():
t1 = time.time()
func()
t2 = time.time()
print("{name}执行时长是: ".format(name=func.__name__), t2-t1, '秒')
return wapper
@func_cost
def slow_func1():
time.sleep(0.1) # 这个很快
@func_cost
def slow_func2():
time.sleep(3) # 假装很慢
def run():
for i in range(20):
slow_func1()
slow_func2()
run() # 执行程序
在需要测量执行时长的函数上方加上一行代码@func_cost
@func_cost
def slow_func1():
它的作用等价于
slow_func1 = func_cost(slow_func1)
这种语法的好处在于,装饰器与函数紧挨着,想要去除装饰器效果时,只需要删除代码即可,而且,代码看起来也更加简练了,当然,你不懂原理,看起来也很懵逼。
本文从原理上为你讲解装饰器,一些更加核心的深层技术,我没有做讲解,以避免你为了学习一个知识点不得不学习更多的知识点。
本文最终编写的func_cost装饰器不能用于实际工作,因为它不是一个完善的装饰器,还不能装饰有参数的函数。如果你想更全面的学习装饰器,可以到装饰器章节 更深入的学习
QQ交流群: 211426309