装饰器(Decorator)是 Python 中一种强大而灵活的语法特性,用于修改或扩展函数或方法的行为。装饰器本质上是一个函数,它接受一个函数作为输入,并返回一个新的函数。装饰器通常用于修改函数的功能、添加额外的逻辑、或者执行预处理和后处理等操作。
装饰器本质上是一个Python函数(其实就是闭包),它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。装饰器用于有以下场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
装饰器的使用符合了面向对象编程的开放封闭原则。
开放封闭原则主要体现在两个方面:
- 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
- 对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。
使用装饰器之前,我们要知道,其实python里是万物皆对象,也就是万物都可传参。
函数也可以作为函数的参数进行传递的。
通过下面这个简单的例子可以更直观知道函数名是如何直接作为参数进行传递
def jzq():
print("我是jzq")
def blog(name):
print("进入blog函数")
name()
print("退出blog函数")
if __name__ == '__main__':
func = jzq # 这里把jzq这个函数名赋值给变量func
func() # 执行func函数
print("*" * 20)
blog(jzq) # 把jzq这个函数当做参数传递给blog函数
执行结果如下所示:
接下来,我想知道这jzq和blog
两个函数分别的执行时间是多少,我就把代码修改如下:
import time
def jzq():
start = time.time()
print("我是jzq")
time.sleep(2)
print(f"执行时间为:{time.time() - start}")
def blog(name):
start = time.time()
print("进入blog函数")
name()
print("退出blog函数")
print(f"执行时间为:{time.time() - start}")
if __name__ == '__main__':
func = jzq # 这里把jzq这个函数名赋值给变量func
func() # 执行func函数
print("*" * 20)
blog(jzq) # 把jzq这个函数当做参数传递给blog函数
上述的改写已经实现了我需要的功能,但是,当我有另外一个新的函数【python_blog_list】,具体如下:
def python_blog_list():
print("""[Python IO流]
https://blog.csdn.net/Fddadd/article/details/132813191""")
print("""[Python面向对象设计原则]
https://blog.csdn.net/Fddadd/article/details/132134146""")
也需要计算函数执行时间的,那按之前的逻辑,就是改写如下:
def python_blog_list():
start = time.time()
print("""[Python IO流]
https://blog.csdn.net/Fddadd/article/details/132813191""")
print("""[Python面向对象设计原则]
https://blog.csdn.net/Fddadd/article/details/132134146""")
print("函数执行时间:{}".format(time.time() - start))
如果也要这样子写的话,不就重复造轮子了吗?虽说人类的本质是鸽王和复读机,但作为一个优秀的cv攻城狮(ctrl+c和ctrl+v)肯定是要想办法偷懒的呀
装饰器,就是可以让我们拓展一些原有函数没有的功能。
基于上面的函数执行时间的需求,我们就手写一个简单的装饰器进行实现。
import time
def jzq():
print("我是jzq")
time.sleep(2)
def count_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print(f"执行时间为:{end - start}")
return wrapper
if __name__ == '__main__':
# 因为装饰器 count_time(jzq) 返回是函数对象 wrapper ,这条语句相当于 jzq = wrapper
jzq = count_time(jzq)
# 执行jzq() 就相当于执行 wrapper()
jzq()
这里的count_time是一个装饰器,装饰器函数里面定义一个wrapper函数,把func这个函数当作参数传入,函数实现的功能是把func包裹起来,并且返回wrapper函数。wrapper函数体就是要实现装饰器的内容。
当然,这里的wrapper函数名是可以自定义的,只要你定义的函数名,跟你return的函数名是相同的就好了
你如果看过其他python项目里面的代码,难免会看到@符号,这个@符号就是装饰器的语法糖。因此上面简单的装饰器还是可以通过语法糖来实现的,这样就可以省去
jzq = count_time(jzq)
这一句代码,而直接调用jzq()这个函数
换句话说,其实使用装饰器的是,默认传入的参数就是被装饰的函数。
import time
def count_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print(f"执行时间为:{end - start}")
return wrapper
@count_time
def jzq():
print("我是jzq")
time.sleep(2)
if __name__ == '__main__':
# # 因为装饰器 count_time(jzq) 返回是函数对象 wrapper ,这条语句相当于 jzq = wrapper
# jzq = count_time(jzq)
# # 执行jzq() 就相当于执行 wrapper()
# jzq()
jzq()
当我们的被装饰的函数是带参数的,此时要怎么写装饰器呢?
上面我们有定义了一个blog函数是带参数的
def blog(name):
print("进入blog函数")
name()
print("退出blog函数")
此时我们的装饰器函数要优化一下下,修改成为可以接受任意参数的装饰器
def count_time(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print(f"执行时间为:{end - start}")
return wrapper
此处,我们的wrapper函数的参数为*args和**kwargs,表示可以接受任意参数
这时我们就可以调用我们的装饰器了。
import time
def count_time(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print(f"执行时间为:{end - start}")
return wrapper
@count_time
def blog(name):
print("进入blog函数")
name()
print("退出blog函数")
if __name__ == '__main__':
def jzq():
print("我是jzq")
time.sleep(1)
# blog() # TypeError: blog() missing 1 required positional argument: 'name'
blog(jzq)
前面咱们知道,装饰器函数也是函数,既然是函数,那么就可以进行参数传递,咱们怎么写一个带参数的装饰器呢?
前面咱们的装饰器只是实现了一个计数,那么我想在使用该装饰器的时候,传入一些备注的msg信息,怎么办呢?咱们一起看一下下面的代码
import time
def count_time_args(msg):
def count_time(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print(f"[{msg}]执行时间为:{end - start}")
return wrapper
return count_time
@count_time_args("fun_one")
def func_one():
print("fun_one")
time.sleep(1)
@count_time_args("fun_two")
def func_two():
print("fun_two")
time.sleep(1)
@count_time_args("fun_three")
def func_three():
print("fun_three")
time.sleep(1)
if __name__ == '__main__':
func_one()
func_two()
func_three()
"""
fun_one
[fun_one]执行时间为:1.0002338886260986
fun_two
[fun_two]执行时间为:1.0010912418365479
fun_three
[fun_three]执行时间为:1.000817060470581
"""
基于原来的count_time函数外部再包一层用于接收参数的count_time_args,接收回来的参数就可以直接在内部的函数里面调用了。
上面咱们一起学习了怎么写装饰器函数,在python中,其实也可以同类来实现装饰器的功能,称之为类装饰器。类装饰器的实现是调用了类里面的__call__函数。类装饰器的写法比我们装饰器函数的写法更加简单。
当我们将类作为一个装饰器,工作流程:
通过__init__()方法初始化类
通过__call__()方法调用真正的装饰方法