装饰器的执行逻辑
import functools
def wrapper(func):
print("This is wrapper") 1⃣️
@functools.wraps(func)
def inner(*args,**kwargs):
print("This is inner") 2⃣️
return func(*args, **kwargs)
return inner
@wrapper
def test():
print("This is test") 3⃣️
return True
# 因为有 @ 的原因,不增加main方法,
直接运行此文件,会执行wrapper中的代码 1⃣️ ,输出 “This is wrapper”, 其他代码不会执行
if __name__ == '__main__':
test()
“”“ 如果增加了main方法,运行此文件,输出顺序为 1⃣️ 2⃣️ 3⃣️
多个装饰器的执行顺序
import functools
def wrapper(func):
print("This is wrapper") 1⃣️
@functools.wraps(func)
def inner(*args,**kwargs):
print("This is inner") 2⃣️
return func(*args, **kwargs)
return inner
def wrapper2(func):
print("This is wrapper2") 4⃣️
@functools.wraps(func)
def inner(*args, **kwargs):
print("This in wrapper2 inner") 5⃣️
return func
@wrapper2
@wrapper
def test():
print("This is test") 3⃣️
return True
“”“
同样,如果不添加main方法,直接运行因为@的存在会执行wrapper
和wrapper2的部分代码,顺序是先输出 1⃣️ ,后输出 4⃣️ ,且仅输出这些内
容,原则是距离被装饰的函数 test 越近的装饰器越先被执行
”“”
if __name__ == '__main__':
test()
添加main方法后,运行此文件,仍然是按照距离越近的越先被调用,即先输出1⃣️4⃣️,之后的输出却是执行wrapper2的inner,即输出5⃣️,再执行wrapper的inner,输出2⃣️,最后执行被装饰的函数的逻辑,输出3⃣️。所以最终的输出顺序是1⃣️ 4⃣️5⃣️2⃣️3⃣️
带有参数传递的装饰器
# 场景:在日志输出被装饰函数的执行耗时场景中,可以在inner中调用执行被装饰函数,并在其前和其后分别获取time.time()变量,减法的结果输出到日志。但日志输出通常需要伴随被装饰函数或者api的信息,以确定是哪个函数名或api名耗时,就需要传递参数给装饰器
import time
from functools import wraps
def time_cost(api_name=None):
def decorator(func):
@wraps(func)
def inner(*args,**kwargs):
start_time = time.time() # 前处理逻辑
result = func(*args,**kwargs) # 调用被装饰函数
end_time = time.time() # 后处理逻辑
cost_time = (end_time-start_time)*1000
print("{} costs time:{:.4f} ms".format(api_name, cost_time))
return result
return inner
return decorator
# 如果要给装饰器传参,需要外层再加一层函数
@time_cost("test_api")
def test():
pass
使用类定义装饰器
class Decorator:
def __init__(self, func): # 初始化传参是 被装饰的函数对象
self.func = func
def __call__(self, *args, **kwargs): # 使用 __call__ 内嵌函数实现
print("This is __call__")
return self.func(*args, **kwargs)
@Decorator
def test():
print("This is test")
if __name__ == '__main__':
test()
>>>
This is __call__
This is test
# 为什么要使用类定义装饰器,在面对逻辑比较复杂的装饰器定义的时候,
# 使用类,可以降低代码逻辑的复杂度
使用类定义装饰器实现简单的缓存 (主要应用了类成员属性的特性)
import time
class Cache:
__cache = {}
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
if self.func.__name__ in Cache.__cache:
print('已经在缓存中,直接从中获取值')
return Cache.__cache.get(self.func.__name__)
else:
print("未在缓存中,直接计算")
value = self.func(*args, **kwargs)
Cache.__cache.update({self.func.__name__: value})
return value
@Cache
def test(param):
time.sleep(5)
return param
if __name__ == '__main__':
for i in range(5):
print(test(i))
>>>
未在缓存中,直接计算
0
已经在缓存中,直接从中获取值
0
已经在缓存中,直接从中获取值
0
已经在缓存中,直接从中获取值
0
已经在缓存中,直接从中获取值
0
# 输出的结果,只有第一行结果输出后,等待了5s,其余的内容都是瞬间输出的,
#且添加了缓存之后,如果我们期望每次调用如果缓存中有数据,就获取缓存的数据
#而非再次调用原函数,就可以对目标函数增加缓存装饰器,以达到快速获取数据的效果
总结及注意点:
- 装饰器的inner函数,通常用 @functools.wraps(func)装饰,以保留被装饰函数的原本的属性,如:不使用functools.wraps(func)装饰,main 中打印 print(test.__name__)输出不是test,而是装饰器函数中的inner的名称。多个装饰器的情况下,会输出最后一个不使用functools装饰的inner。
- 装饰器的inner函数,返回值为 func(*args,**kwargs) ,是 func()执行结果 而非 func 函数对象,如果仅返回func对象,则被装饰函数将不会被执行,仅执行装饰器和inner的逻辑
- 多个装饰器,执行逻辑按照先执行距离被装饰函数较近的装饰器,由近及远,然后执行inner的逻辑,由远及近,最后执行被装饰的函数逻辑
- 如果需要增加被装饰函数执行后的逻辑,可以在 inner 中调用被装饰函数 result=func(*args,**kwargs),在其后增加后处理逻辑,最后return result,此方法常见的是日志输出被装饰函数的运行时间,在函数执行前和结束后分别获取time.time(),执行减法输出在日志
引申:
-
装饰器传递参数,在外层再包一层函数的操作,其实是python中闭包的使用
-
闭包,说白了是在一个函数内部定义一个函数,并将此函数对象返回,如:
def test(*args,**kwargs): def inner(): pass return inner if __name__ == '__main__': func = test() func() # 可以直接调用 test 中定义的函数 inner
比较官方的说法: 闭包是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行,这样一个函数称为闭包;
Python闭包有助于避免使用全局值,并提供某种形式的数据隐藏;
一个用于理解闭包的例子:def test(): local_list = [] number = 0 def inner(num): local_list.append(number+num) print("local_list:{}, number:{}".format(local_list,number)) return inner if __name__ == '__main__': func01 = test() func01(1) func01(2) func01(3) func02 = test() func02(1) func01(4) func02(2) >>> local_list:[1], number:0 local_list:[1, 2], number:0 # 只要func01对象没有被销毁,其定义的作用域中的变量也就不会被销 #毁,尽管test方法已经执行完毕,但内部的变量 local_list 会伴随实例 # func01 的存在而存在 local_list:[1, 2, 3], number:0 local_list:[1], number:0 local_list:[1, 2, 3, 4], number:0 local_list:[1, 2], number:0
func01 和func02 可以认为是 test 闭包的两个实例;
实例 func01 有一个与其对应的 local_list (称为自由变量),func02也有一个与其对应的自由变量,两者调用各自的自由变量相互不受影响;
但是一个实例的自由变量的修改,会传递给下一次闭包的调用。