浅谈装饰器
装饰器
装饰器本事是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志,性能测试,事务处理,缓存、权限校验等场景。
装饰器的作用:
1.增加额外功能。一般为有共性的额外功能。
2.不修改被装饰的函数的源代码和调用方式。
3.分离了业务函数和非业务函数。增加了装饰函数的*性、复用性。
例如我有一个加法add函数,需要添加一个日志打印loging功能。
思路一,在加法函数代码上添加。
def add(x,y,z=7):
print('this is a {} func'.format(add.__name__))
return x+y+z
这种思路侵入了add函数,又破坏了日志打印功能的复用性。
肯定是不可取的。弃用。
思路二,定义一个loging函数。
def add(x,y,z=7):
return x+y+z
def loging(fn):
print("this fun is {}".format(fn.__name__))
res=fn(4,5)
return res
print(loging(add))
做到了业务分离,但是fn函数传参是个问题。
改进版本:
def add(x,y,z=7):
return x+y+z
def loging(fn,x,y):
print("this fun is {}".format(fn.__name__))
res=fn(x,y)
return res
print(loging(add,4,5))
参数*度不高,继续改进,使用无敌参数:
def add(x,y,z=7):
return x+y+z
def loging(fn,*args,**kwargs):
print("this fun is {}".format(fn.__name__))
res=fn(*args,**kwargs)
return res
print(loging(add,4,5))
之前执行业务逻辑时,使用add(4,5),现在不得不使用loging(add,4,5),有没有更好的方式
呢?继续改进:
def add(x,y,z=7):
return x+y+z
def loging(fn):
def _loging(*args,**kwargs):
print("this fun is {}".format(fn.__name__))
res=fn(*args,**kwargs)
return res
return _loging
add=loging(add)
print(add(4,5))
貌似很接近了,就是多了一句add=loging(add)。有没有方法去掉呢,当然有,答案就是装饰
器。
装饰器是一个语法糖,在定义函数前+ @装饰函数, 相当于一句赋值:
被装饰函数 = 装饰函数(被装饰函数)。。。概念什么的最烦了。
简单说就是add函数前写@loging,就省去一句add=loging(add)。
太好了,目标达成,啦啦啦。
def loging(fn):
def wrapper(*args,**kwargs):
print("this fun is {}".format(fn.__name__))
res=fn(*args,**kwargs)
return res
return wrapper
@loging # 相当于add=loging(add)
def add(x,y,z=7):
return x+y+z
print(add(4,5))
###########分割#################################
继续科普概念。。。:
装饰器在python使用如此方便都要归因于python的函数能像普通的对象一样能作为参数传递给其
他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另一个函数内。
装饰器是一个函数,函数作为它的形参,返回值也是一个函数。
functools模块
上面的例子中,虽然add函数的源代码和调用方法没有发生改变,但是add函数实际上已经被
@loging # 相当于add=loging(add)重新定义为一个增强版的add,这就会产生一个副作用,
即:原函数对象的属性被替换了。add的属性被替代为了wrapper的属性!!好在我们有
functools.wraps,wraps本身也是一个装饰器,它能把原参数的元信息拷贝到装饰器函数中,这
使得装饰器函数也有和原函数一样的元信息了。
用法:
@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS,
updated=WRAPPER_UPDATES)
类似copy_properties功能
wrapped 被包装函数
元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性
'__module__', '__name__', '__qualname__', '__doc__', '__annotations__'
模块名、名称、限定名、文档、参数注解
元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典
增加一个__wrapped__属性,保留着wrapped函数
import functools
def loging(fn):
@functools.wraps(fn)
def wrapper(*args,**kwargs):
print("this fun is {}".format(fn.__name__))
res=fn(*args,**kwargs)
return res
return wrapper
@loging # 相当于add=loging(add)
def add(x,y,z=7):
return x+y+z
print(add(4,5))
print(add.__name__)
带参装饰器:
装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,如loging,
该装饰器唯一的参数就是执行业务的函数。装饰器的语法允许我们在调用时,提供其他参数,
如:@decorator(a),这样,就为装饰器的编写和使用提供了更大的灵活性。
def loging(level):
def decorator(fn):
def wrapper(*args,**kwargs):
print("this fun is {},level is {}".format(fn.__name__,level))
res=fn(*args,**kwargs)
return res
return wrapper
return decorator
@loging(level='warn') # 相当于add=loging(level)(add) decorator(add)
def add(x,y,z=7):
return x+y+z
print(add(4,5))