一. 装饰器使用场景引出
我们在开发中会遇到这样的问题:现在项目中有许多已经定义好的函数,我们现在需要测试每一个函数的运行时间。这个问题我们可以想到在每一个函数中加入测试函数运行时间的代码,假如有1000个函数,这种方法就需要在这1000个函数里面分别添加测试运行时间的功能代码。这样不仅代码体积变大,而且还有误改造成的灾难性后果。所以,装饰器是一种非常有效的手段。
二. 什么是装饰器
装饰器本质是一个函数,它是在不修改其他函数源代码与调用方式的前提下,为其他函数添加新功能的工具。
在一个软件上线之后,就应当遵循开放封闭原则,即对修改原代码是封闭的,对功能的扩展是开放的,也就是在不修改一个源代码以及不改变调用的方式的前提下,为其加上新功能。
在使用装饰器之前我们来明确几个定义
1.高阶函数
高阶函数是能以函数作为参数的函数或者返回值是函数的函数。
def fun1():
print("我在执行fun1")
def fun2(arg): #以函数作为参数
return arg #返回值是函数
fun2(fun1)()
2.嵌套函数
在函数内定义函数,即为嵌套函数。
接下来我们真正的来一步一步的认识其实很简单的装饰器。
三. 实例
项目需求:测试一个函数的执行时间(当然,下面的程序要导入time模块)
def func():
time.sleep(1)
print("我在执行func")
想法1:我们理所当然的可以这样去做:获取开始时间starttime,执行函数,获取函数执行完结束的时间endtime,那么函数执行的时间为endtime-starttime。(这边的执行时间不是这么简单的计算,其实starttime = time.time()和endtime = time.time()也是需要时间的)
starttime = time.time()
func()
endtime = time.time()
print("func执行了%s秒"%(endtime-starttime))
如果项目中有非常非常多的函数,要想读取全部时间来测试性能,你就必须得不断重复starttime = time.time()和endtime = time.time()这两句话,这将对项目性能造成很大的影响。
想法2:我们可不可以定义一个函数来测试来减少代码的复用?我们来定义一个函数get_time。
def get_time(foo):
starttime = time.time()
foo()
endtime = time.time()
print("func执行了%s秒" % (endtime - starttime))
get_time(func)
这样的话,我们只要函数调用get_time(func)即可,减少了代码的冗余。但是我们并没有从根本上解决这个问题,因为我们改变了函数的调用方式。之前的调用方式是func(),而现在变成了get_time(func),相当于我们调用了别的函数。
接下来我们就使用装饰器,既能使函数调用方式不变,又能让全部函数去使用。
我们能不能把
starttime = time.time()
foo()
endtime = time.time()
print("func执行了%s秒" % (endtime - starttime))
这四句代码返回出去?
答案是当然可以
def get_time(foo):
def inner():
starttime = time.time()
foo()
endtime = time.time()
print("func执行了%s秒" % (endtime - starttime))
return inner
我们接下来先分析一下这个嵌套函数,并让它被调用
def get_time(foo):
def inner():
starttime = time.time()
foo()
endtime = time.time()
print("func执行了%s秒" % (endtime - starttime))
return inner
new_func = get_time(func)
print(new_func)
我们把func传到了get_time中,get_time返回的是inner,但是它并没有执行函数体!!!执行结果如下,很明白,返回了局部函数inner的地址,我们用new_func来接收这个返回结果。
<function get_time.<locals>.inner at 0x00000243737C6C80>
如果想执行,把print(new_func)换为new_func()即可,new_func是一个变量,而new_func()才是真正的调用。
def get_time(foo):
def inner():
starttime = time.time()
foo()
endtime = time.time()
print("func执行了%s秒" % (endtime - starttime))
return inner
new_func = get_time(func)
new_func()
我们定义的变量是new_func,依旧是使用new_func()这种方式调用,仍没有解决调用方式的问题。如果我们这样改
def get_time(foo):
def inner():
starttime = time.time()
foo()
endtime = time.time()
print("func执行了%s秒" % (endtime - starttime))
return inner
func = get_time(func)
func()
我们把new_func改成了func,非常的明显,调用方式正确了,使用了func()这种正确的调用方式。
到这里,我们就非常的接近装饰器了。在Python中有一个特殊的语法糖@。
我们来看下面两段代码:
#使用装饰器
def get_time(foo):
def inner():
starttime = time.time()
foo()
endtime = time.time()
print("func执行了%s秒" % (endtime - starttime))
return inner
@get_time #等价于func=get_time(func)
def func():
time.sleep(1)
print("我在执行func")
func()
#没有使用装饰器
def get_time(foo):
def inner():
starttime = time.time()
foo()
endtime = time.time()
print("func执行了%s秒" % (endtime - starttime))
return inner
def func():
time.sleep(1)
print("我在执行func")
func = get_time(func)
func()
我们可以看到,两段代码的区别就是@get_time换掉了func = get_time(func)。这里我们便实现了一个装饰器get_time,func是被修饰函数(原函数)。我们可以看到,我们并没有修改func()的源代码,也没有修改func()的调用方式。使用了装饰器函数之后,不管在哪里使用func(),它都被附加了装饰器新增的功能。综上来说,装饰器是一个可以给其他函数增加功能的函数,然后使用“@装饰器函数名”给需要添加功能的函数修饰即可。
注意:装饰器一定要在被装饰函数之前定义!
装饰器的进阶用法请看下一篇装饰器(二)