前言
装饰器可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象,本质上就是提供一个 入参是函数 返回值也是函数的 函数。装饰器用于有以下场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
举例:
现在有一个函数
def business_logic():
"""
业务逻辑代码
"""
print("此乃你的逻辑")
线上需要收集该函数执行前后的数据,但是我们不想改动business_logic函数,因为它已经足够复杂了,我们可以按照如下方式去做
def get_data(func):
print("执行前收集数据")
func()
print("执行后收集数据")
def business_logic():
"""
业务逻辑代码
"""
print("此乃你的逻辑")
get_data(business_logic)
这样我们可以达到不改动business_logic从而收集数据,但是,我们以前调用business_logic,现在却需要调用get_data,改变了代码逻辑,这不好,如果调用business_logic的地方很多,那加一个这个逻辑代价太大了,还不如直接修改business_logic,那我们再改进一下,也就是做出所谓的装饰器
def get_data(func):
def wrapper():
print("执行前收集数据")
func()
print("执行后收集数据")
return wrapper
def business_logic():
"""
业务逻辑代码
"""
print("此乃你的逻辑")
business_logic = get_data(business_logic)
business_logic()
print(business_logic.__name__)
结果如下
不难看出get_data装饰器就是传入了函数,返回了新的函数,然后将旧的business_logic覆盖,执行的根本就是新的闭包函数wrapper,看起来满足了需求,但是这样做还有一个小问题,就是会多一步赋值操作:
函数装饰器
先修改上级文章中的代码
def get_data(func):
def wrapper(*args, **dargs):
print("执行前收集数据")
func(*args, **dargs)
print("执行后收集数据")
return wrapper
@get_data
def business_logic(param: str):
"""
业务逻辑代码
"""
print("此乃你的逻辑,你的参数是 %s" % param)
business_logic("chenchen")
print(business_logic.__name__)
print(business_logic.__doc__)
执行结果如下,效果不错
但是还个问题,business_logic的元数据好像不对__name__变成了wrapper,__doc__变成了None,这不合理,我们先来看一下正常的
def business_logic(param: str):
"""
业务逻辑代码
"""
print("此乃你的逻辑,你的参数是 %s" % param)
print(business_logic.__name__)
print(business_logic.__doc__)
结果如下,很明显不对
其实上面@符号语法糖和上级文章中的代码逻辑是一样,如下图
那么我们可以通过functools提供的wraps装饰器来将元数据一起复制回来
from functools import wraps
def get_data(func):
@wraps(func)
def wrapper(*args, **dargs):
print("执行前收集数据")
func(*args, **dargs)
print("执行后收集数据")
return wrapper
@get_data
def business_logic(param: str):
"""
业务逻辑代码
"""
print("此乃你的逻辑,你的参数是 %s" % param)
business_logic("chenchen")
print(business_logic.__name__)
print(business_logic.__doc__)
结果如下
很明显变回来了,问题似乎已经完美解决了,但是又有新的要求了,我们收集数据的格式不同,上面的
只是其中一种,我们还有一种格式如下
print("新-执行前收集数据")
func(*args, **dargs)
print("新-执行后收集数据")
这时候我们该如何判断呢?我们可以通过给装饰器传递参数来解决这个问题吧?比如:
from functools import wraps
def get_data(func, param1):
@wraps(func)
def wrapper(*args, **dargs):
print("执行前收集数据")
func(*args, **dargs)
print("执行后收集数据")
@wraps(func)
def wrapper_new(*args, **dargs):
print("新-执行前收集数据")
func(*args, **dargs)
print("新-执行后收集数据")
ret_wrapper = wrapper
if param1 == "123":
ret_wrapper = wrapper_new
return ret_wrapper
@get_data
def business_logic(param: str):
"""
业务逻辑代码
"""
print("此乃你的逻辑,你的参数是 %s" % param)
business_logic("chenchen")
print(business_logic.__name__)
print(business_logic.__doc__)
似乎没毛病奥铁子,我们执行看看:
我*****,日了狗了,语法糖只允许给一个参数,那就做个递归吧:
from functools import wraps
def get_data(param1):
def get_data_func(func):
@wraps(func)
def wrapper(*args, **dargs):
print("执行前收集数据")
func(*args, **dargs)
print("执行后收集数据")
@wraps(func)
def wrapper_new(*args, **dargs):
print("新-执行前收集数据")
func(*args, **dargs)
print("新-执行后收集数据")
ret_wrapper = wrapper
if param1 == "123":
print(1111111)
ret_wrapper = wrapper_new
return ret_wrapper
return get_data_func
@get_data("123")
def business_logic(param: str):
"""
业务逻辑代码
"""
print("此乃你的逻辑,你的参数是 %s" % param)
@get_data("12345")
def business_logic_new(param: str):
"""
业务逻辑代码
"""
print("此乃你的逻辑,你的参数是 %s" % param)
business_logic("chenchen")
print(business_logic.__name__)
print(business_logic.__doc__)
business_logic_new("chenchen12")
print(business_logic_new.__name__)
print(business_logic_new.__doc__)
结果如下
看起来很复杂,其实就是下面的逻辑
def business_logic(param: str):
"""
业务逻辑代码
"""
print("此乃你的逻辑,你的参数是 %s" % param)
def wrapper(*args, **dargs):
print("执行前收集数据")
func(*args, **dargs)
print("执行后收集数据")
func1 = get_data("123")
func2 = func1(business_logic)
result = func2("chenchen")
这样好理解了吧,就是多套一层娃而已。
好啦,函数装饰器就到这里结束了,欢迎大家留言提意见 或 指正错误
类装饰器
顾名思义,就是 @ + 类名的语法糖
1、利用__get__
class GetData:
def __init__(self, func):
self.func = func
def __get__(self, instance, cls):
"""
:param:
instance: 即为调用装饰器的实例对象
cls: 即为类对象
"""
def testAAA(param: str):
print("执行前 - %s" % param)
self.func(param)
print("执行后 - %s" % param)
pass
return testAAA
class B:
def __init__(self):
pass
@GetData
def test(param):
print("业务逻辑 - %s" % param)
b = B()
print(b.test)
print(type(b.test))
b.test("123")
结果如下
其实可以理解为,test方法被替换成了testAAA,还是元数据丢失的问题,可以使用如下方法来解决,还是wraps装饰器
from functools import wraps
class GetData:
def __init__(self, func):
self.func = func
def __get__(self, instance, cls):
"""
:param:
instance: 即为调用装饰器的实例对象
cls: 即为类对象
"""
@wraps(self.func)
def testAAA(param: str):
print("执行前 - %s" % param)
self.func(param)
print("执行后 - %s" % param)
pass
return testAAA
class B:
def __init__(self):
pass
@GetData
def test(param):
print("业务逻辑 - %s" % param)
b = B()
print(b.test)
print(b.test.__name__)
print(type(b.test))
b.test("123")
执行结果如下
可以看到正常了
2、利用__call__
class GetData:
def __init__(self, func):
self.func = func
def __call__(self,param):
"""
:param:
instance: 即为调用装饰器的实例对象
cls: 即为类对象
"""
print("执行前 - %s" % param)
self.func(param)
print("执行后 - %s" % param)
pass
class B:
def __init__(self):
pass
@GetData
def test(param):
print("业务逻辑 - %s" % param)
b = B()
print(b.test)
print(type(b.test))
b.test("123")
执行结果如下
可以看到B.test成了 GetData 这个类的实例,相当于B的内部类,执行b.test("123")这样像调用函数一样,就会调用__call__
最后说一下:
1、__call__和 __get__同时存在的话,装饰器会采用__get__,这里我觉得应该是底层有判断,我后续还会深究,写新的文章引用在这里。
2、__call__、__get__等是什么我后续会写学习心得,引用在这里