目录
装饰器说明
器指的工具(只要是工具,就应该想到函数),装饰指的是为被装饰对象添加新功能,需要注意的是:项目一旦上线之后,就应该遵循开发封闭的原则。开发封闭指的是对修改函数内的源代码和调用方式是封闭的,对功能的扩展是开放的。
看起来有点矛盾,在这样的要求下,必须找到一种解决方案,能够在不修改一个函数源代码以及调用方式的前提下,为其添加新功能,这就用到了装饰器,它能够在不修改被装饰对象源代码与调用方式的前提下,为被装饰对象添加新功能。
简单理解,就是如果在不修改函数的情况下,怎么给函数添加新功能。我们可以很轻松地想到,将这个函数套在另外一个函数中(这个函数就是装饰器)就可以了。当要给函数添加新功能时,在不修改原函数的情况下,新添加的功能可以在新函数中实现。
比如函数fun1()实现功能ast1,现在要求fun1()函数实现ast1和ast2功能,但是要求不能修改函数fun1()的源代码,这个时候可以新建一个函数fun2(),函数fun2()以函数fun1()作为参数,同时在fun2()中实现ast2功能,这样就既能实现功能ast1,ast2,同时又不用修改fun1()函数。
def fun1():
ast1
def fun2(fun1):
fun1()
ast2
那这里就有点疑惑了,这样在调用函数的时候,不就是调用fun2()了吗?fun1()没有被显式地调用啊。为了解决这个问题,就用到了我们说的装饰器。现在可以先简单理解,装饰器的作用是为原函数添加新功能,同时又不修改原函数,同时又能做到显式调用原函数而不是调用装饰器。
装饰器分为无参装饰器和有参装饰器,下面我们分别说明一下无参装饰器和有参装饰器。
1、无参装饰器
无参装饰器指装饰器本身没有参数。
先抛弃装饰器,假设现在要给index函数添加一个统计时间的功能,index函数原型如下:
import time # 负责计时的模块
def index():
time.sleep(3) # 停止3秒
print('Wlecome to index page')
index()
方法一:使用time.time()函数
import time # 负责计时的模块
def index():
time.sleep(3) # 停止3秒
print('Wlecome to index page')
start_time = time.time()
index()
end_time = time.time()
print('running time is {} s'.format(end_time - start_time))
这种方法简单地实现了对index()函数的计算,但是如果需要对多个函数进行计时,这种办法的缺点就暴露了,我们需要为每个函数都调用time.time(),这样会造成代码大量重复。
因此,我们可以将计时功能通过集成为一个函数,函数的输入参数为我们要进行计时的函数index(),这就是我们要介绍的方法二。
方法二:创建计时函数实现计时功能
def index():
time.sleep(3) # 停止3秒
print('Wlecome to index page')
def wrapper(func):
start_time = time.time()
func() # index()
end_time = time.time()
print('running time is {} s'.format(end_time - start_time))
wrapper(index)
代码中wrapper()函数实现了计时功能,wrapper()函数有一个输入参数func,func是需要被计时的函数,这里我们使用index()函数,这是通过函数调用的方式实现计时,当多个函数需要被计时时,可以直接通过调用函数实现。
但是这里又出现了一个问题,我们是希望在index()中添加计时功能,可以直接调用index()实现计时,但是方法二中需要显式调用wrapper()才能实现计时,这显然不是我们的需求。所以还需要改进,在方法三种我们使用闭包函数来实现使用index()就能进行计时。
方法三:使用闭包函数避免显式调用wrapper()函数
import time # 负责计时的模块
def index():
time.sleep(3) # 停止3秒
print('Wlecome to index page')
def timer(func): # 闭包函数
# func = index
def wrapper(): # 注意wrapper不需要传入参数
start_time = time.time()
func() # index()
end_time = time.time()
print('running time is {} s'.format(end_time - start_time))
return wrapper
index = timer(index) # 赋值给index覆盖原来的index,index=wrapper
index() # wrapper()
这种情况下,我们就能通过调用index()函数实现计时功能,同时index()源码也没有被修改。outer()就是一个装饰器,也就是说我们一步步得到了装饰器,装饰器在不修改原函数源代码的情况下,给原函数添加了新功能。当然了,这不是最终的装饰器,只能说这个方法方法基本实现了装饰器的功能。
也就是说,装饰器其实是一个函数,这个函数的作用是新定义一个函数,并且将这个函数返回。
但是现在这个方法有一个问题,如果我们需要被装饰的函数有返回值,上面这个方法得到的函数是没有返回值的,因此我们还需要对这个方法进行修改,使其可以返回值。
通过上面的代码我们可以知道,index实际上变为wrapper函数,如果原来的index()函数拥有返回值,那么只需要在wrapper()函数中保存index()的返回值,并在wrapper()函数最后返回该值就可以了。
具体的实现可以看方法四。
方法四:解决没有返回值问题
import time # 负责计时的模块
def index():
time.sleep(3) # 停止3秒
print('Wlecome to index page')
return 1 # index()返回值
def outer(func):
# func = index
def wrapper():
start_time = time.time()
res = func() # index() # wrapper保存index的返回值
end_time = time.time()
print('running time is {} s'.format(end_time - start_time))
return res # 返回index的返回值
return wrapper
index = outer(index) # 赋值给index覆盖原来的index,index=wrapper
res = index() # wrapper()
print(res)
通过上面的方法我们就实现了无法返回返回值的问题。
现在我们实现了返回值的问题,但是还有另外一个问题,如果index()函数有参数,比如index(N),这种情况下wrapper()怎么识别index()函数是否有参数呢?
实现也很简单,index最后其实就是wrapper函数,只要将wrapper的参数和index的参数进行关联就可以了,具体实现参数传递可以看方法五。
方法五:解决参数传递问题
import time # 负责计时的模块
def index(N): # 参数N是停止时间参数
time.sleep(N) # 停止N秒
print('Wlecome to index page')
return 1
def outer(func):
# func = index
def wrapper(*args,**kwargs): # *args和**kwargs用于接收index的参数
start_time = time.time()
res = func(*args,**kwargs) # *args和**kwargs和wrapper的参数是关联的
end_time = time.time()
print('running time is {} s'.format(end_time - start_time))
return res
return wrapper
index = outer(index) # 赋值给index覆盖原来的index,index=wrapper
res = index(4) # wrapper(N),需要传入参数
print(res)
方法中的*args和**kwargs的功能是获取参数,参数的个数不限定,wrapper获取参数后,会传递给index执行,这样就解决了参数传递问题。
装饰器模板
这样从方法一到方法五,我们从零实现了无参装饰器并解决了装饰器中的问题,使其得到完善。现在我们可以得到无参装饰器的模板:
def outer(func):
def inner(*args,**kwargs):
"""
这里写装饰器逻辑,添加函数功能
:param args:任意位置参数
:param kwargs:任意关键参数
:return:一个函数对象
res = func(*args,**kwargs) # 传递参数和返回值
return res
通过以上方法我们得到了无参装饰器的模板,在我们实际使用装饰器时,我们会使用到装饰器语法糖,下面我们介绍一下什么是装饰器语法糖。
装饰器语法糖
装饰器语法糖就是在被装饰对象正上方单独一行添加@timer()。
import time
# 装饰器也是一个函数,使用函数必须先定义,所以装饰器放在最上方
def timer(func):
def wrapper(*args,**kwargs):
start_time = time.time()
res = func(*args,**kwargs)
stop_time = time.time()
print(end_time - begin_time)
return res
return wrapper
# 装饰器的作用是定义一个新函数,并返回这个新函数
@timer # 在被装饰对象正上方单独一行添加,相当于执行index=timer(index)
def index(N):
time.sleep(N)
print('Welcome to index page')
return 1
res = index(3)
print(res)
2、有参装饰器
装饰器本身是一个函数,函数就可以有参数,有参装饰器就是装饰器本身有一个参数。我们现在看一下有参装饰器和无参装饰器的区别:
import time
# 装饰器也是一个函数,使用函数必须先定义,所以装饰器放在最上方
def decorator(*args):
print('This is a',*args)
def timer(func):
def wrapper(*args,**kwargs):
start_time = time.time()
res = func(*args,**kwargs)
stop_time = time.time()
print(start_time - stop_time)
return res
return wrapper
return timer
# 装饰器的作用是定义一个新函数,并返回这个新函数
@decorator('decorator') # 在被装饰对象正上方单独一行添加,相当于执行index=timer(index)
def index(N):
time.sleep(N)
print('Welcome to index page')
return 1
res = index(4)
print(res)
对于原来的无参装饰器,有参装饰器在timer()函数外再套用了一层函数,这样在@decorator(‘decorator’) 的时候,实际得到的是@timer(func),因为有参装饰器在传入参数’decorator’之后返回的是一个函数timer。