装饰器是python一个重要的部分,由它的名称我们就可以大致了解到它的功能:拓展其他函数。装饰器可以让我们的代码更加简洁,也更加pythonic。
首先,我们先回顾一下基础概念。
一、在python中,如果调用一个函数不带括号时,调用的是这个函数本身,无需等待该函数执行完毕;如果调用一个函数带括号时,调用的是这个函数return的结果,需要等待函数执行完毕的结果。实例展示如下:
def demo1():
print("hello 2022")
demo1()
print(demo1)
#结果依次如下:
#hello 2022
#
二、函数闭包
闭包就是引用自由变量的函数,这个函数保存了执行的上下文,可以脱离原本的作用于存在。
def dec():
para = 'closure'
# 嵌套一层 形成闭包
def wrapper():
print(para)
return wrapper
# 获取一个闭包
closure = dec()
# 执行
closure()
para是一个局部变量,在dec中执行后即被回收。在嵌套函数中使用了这个变量,也就是将局部para变量封闭在嵌套函数中,形成闭包。
三、python装饰器
介绍了以上基础概念之后,我们再来介绍python装饰器的详细用法。
现在有如下代码:
def func():
print("hello")
sleep(5)
print(2022)
现在需求来了,我们想拓展一下原来的函数,在原有的基础下增加一个计算函数执行时间的模块。
我们首先想到的便是编写一个计时函数,可以直接完成我们的需求。代码如下:
from time import time, sleep
def timer():
start = time()
func()
end = time()
res = end - start
print("执行时间为{:.3f}".format(res))
def func():
print("hello")
sleep(5)
print(2022)
if __name__ == "__main__":
timer()
#执行结果如下
#hello
#2022
#执行时间为5.000
现在我们已经完成了对func()函数执行时间的计算,值得庆幸的是我们现在只有一个函数需要计算它的运行时间,如果一个代码中有成千上万计像func()函数都需要计算他们的运行时间该怎么办?总不能为每一个函数添加一个计时器或者不断地修改timer函数,这样不仅对操作员还是机器都是一个巨大的折磨。这时我们便选用@装饰器来解决我们这些问题。
代码展示如下:
from time import time, sleep
def timer(c):
print("this is timer")
def wrapper():
start = time()
c()
end = time()
res = end - start
print("执行时间为{:.3f}".format(res))
return wrapper
@timer
def func():
print("hello")
sleep(5)
print(2022)
if __name__ == "__main__":
func()
"""
执行结果如下
this is timer
hello
2022
执行时间为5.000
"""
通过对比,我们发现timer函数中增加了wrapper函数,timer函数的返回值是wrapper函数,可以粗暴的理解为timer()=wrapper()。
现在来分析一下以上代码的工作流程:@timer及其一下四行为装载装饰器的过程,装饰器timer的参数是一个函数,返回值也是一个函数,作为参数的函数c()就在闭包函数内执行;在func()前使用@timer,就相当于为func()注入了新的功能,我们既不需要入侵原函数,也不用重复执行原函数。
接下来我们继续分析func()有参数的情况。
代码如下:
from time import time, sleep
def timer(c):
print("this is timer")
def wrapper(a, b):
start = time()
c(a, b)
end = time()
res = end - start
print("执行时间为{:.3f}".format(res))
return wrapper
@timer
def func(a, b):
sleep(5)
print("a+b=%d" % (a + b))
if __name__ == "__main__":
func(4, 5)
"""
this is timer
a+b=9
执行时间为5.001
"""
需要注意的是,func()与wrapper()中的参数必须一致。
接下来我们要思考的问题便是:如果func()有无穷多个参数怎么办呢?有没有一个简单的方法可以传递参数?我们想到的是利用位置参数*args和关键字参数**kwargs。(*args与**kwargs的用法不在赘述)具体是怎么实现的呢?我们来看代码。
from time import time, sleep
def timer(c):
print("this is timer")
def wrapper(*args, **kwargs):
start = time()
c(*args, **kwargs)
end = time()
res = end - start
print("执行时间为{:.3f}".format(res))
return wrapper
@timer
def func(a, b):
sleep(2)
print("a+b=%d" % (a + b))
if __name__ == "__main__":
func(4, 5)
"""
this is timer
a+b=9
执行时间为2.000
"""
显而易见,我们大大简化了代码。
接下来有两个例子供大家更好的掌握装饰器的用法。
import time
def timer(func):
print("this is timer")
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
cha_zhi = end - start
print("运行时间为%d" % cha_zhi)
return wrapper
def login(func):
print("this is login")
def wrapper1(*args, **kwargs):
user = input("please input your name:")
password = input("please input your password:")
if user == 'root' and password == 'root':
print("login successful!")
res = func(*args, **kwargs)
return res
else:
print("error!")
return wrapper1
@login
@timer
def index():
time.sleep(0.5)
print("from index")
index()
"""
this is timer
this is login
please input your name:root
please input your password:root
login successful!
from index
运行时间为0
"""
def dec1(func):
print("1")
def one():
print("2")
func()
print("3")
return one
def dec2(func):
print("a")
def two():
print("b")
func()
print("c")
return two
@dec1
@dec2
def test():
print("test")
test()
"""
a
1
2
b
test
c
3
进程已结束,退出代码为 0
"""
来看这块代码:当遇到两个装饰器时,执行的顺序是有下往上,即test=dec1(dec2(test)),先执行dec2(test),输出a,此时dec2()中的参数func指向test,返回two(),然后dec1(two), 输出1,dec1()中func指向two,返回one()。test()是实际被装载的函数,此时实际执行的是one(), 运行到func()时再执行two()。