一、装饰器概述
python的装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。 简单的说装饰器就是一个用来返回函数的函数。 它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计, 有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。 概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。 认识装饰器之前,需要明确的事情: # 装饰器在不改变函数内部的基础上,可以统一添加某些功能,让函数在执行之前或之后做一些操作 # 定义完函数未调用,函数内部不执行 # 函数名(不加括号),代指函数整体;加括号:执行函数 # 函数名可以当作参数传递 # 只要函数用上装饰器,那么函数就会被重新定义为装饰器的内层函数
二、为什么要使用装饰器
1、来个例子说明
首先有个函数:
1 def foo(): 2 print('i am foo')
现在希望记录函数执行的日志,可以写成下面这样:
1 def foo(): 2 print('i am foo') 3 print("foo is running")
那么如果有100个函数都要增加记录日志的需求呢?
这样我们可以写一个专门的函数,让它干记录日志的活:
# 专门记录日志的函数 def use_logging(func): print("%s is running" % func.__name__) func() # 原函数 def bar(): print('i am bar') # 使用,把原函数当作参数传给use_logging use_logging(bar) # 结果如下 bar is running i am bar
三、装饰器入门
1、装饰器语法糖
# python提供了@符号作为装饰器的语法糖,使我们更方便的应用装饰函数。但使用语法糖要求装饰函数必须return一个函数对象。 # 只要函数用上装饰器,那么会发生以下事情: # 原函数就会被重新定义为装饰器的内层函数 # 哪个函数调用了装饰器,就会将哪个函数的函数名当作参数传给装饰器函数 # 将装饰器函数的返回值,重新赋值给原函数 def use_logging(func): # 这里的func就是bar # 这里的inner函数,就是bar函数 def inner(): print("{} is running!".format(func.__name__)) # 新增加的功能 return func() # func就是bar,因为装饰器不能改变原函数,所以还要返回一个老的bar的值 return inner # 返回包装过的函数,这里的inner也就是bar,这里先没有带(),在最后执行bar时(也就是执行inner)才带上() @use_logging #含义:use_logging(bar) def bar(): # 函数bar调用了装饰器,会将bar当参数传给use_logging print("i am bar") bar()
2、带参数的函数使用装饰器
def use_logging(func): def inner(a, b): print("{} is running!".format(func.__name__)) return func(a, b) return inner @use_logging def bar(a, b): print("The sum of a and b is {}".format(a+b)) bar(1, 2) # 我们装饰的函数可能参数的个数和类型都不一样,每一次我们都需要对装饰器做修改吗? # 这样做当然是不科学的,因此我们使用python的变长参数*args和**kwargs来解决我们的参数问题。
如下:
# 这样就可以适应带参数的函数了 def use_logging(func): def inner(*args, **kwargs): print("{} is running!".format(func.__name__)) return func(*args, **kwargs) return inner @use_logging def bar(a, b): print("The sum of a and b is {}".format(a+b)) bar(1, 2)
上面是带参数的函数和不带参数的装饰器,下面看一下带参数的装饰器;
3、带参数的装饰器
# 某些情况我们需要让装饰器带上参数,那就需要编写一个返回一个装饰器的高阶函数,比较复杂 def use_logging(level): def inner1(func): # 这里的func是bar def inner2(*args, **kwargs): if level == "warn": print("{} is running!".format(func.__name__)) return func(*args, **kwargs) return inner2 return inner1 @use_logging(level="warn") def bar(a, b): print("The sum of a and b is {}".format(a+b)) bar(1, 2)
4、functools.wraps
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:
def use_logging(func): def inner(*args, **kwargs): print("%s is running" % func.__name__) func(*args, **kwargs) return inner @use_logging def bar(): print('i am bar') print(bar.__name__) # 结果成了:inner bar() # 结果: bar is running i am bar inner #函数名变为inner而不是bar,这个情况在使用反射的特性的时候就会造成问题。因此引入functools.wraps解决这个问题。
使用functools.wraps:
from functools import wraps # 导包 def use_logging(func): @wraps(func) def inner(*args, **kwargs): print("%s is running" % func.__name__) func(*args, **kwargs) return inner @use_logging def bar(): print('i am bar') print(bar.__name__) # OK了,结果是bar bar() # 结果 bar is running i am bar bar
5、双层装饰器
# 双层装饰器,执行顺序是从上往下执行(outer1-->outer2); 调用(解释)顺序是先outer2再outer1 # 返回顺序也是outer1-->outer2 def outer1(func): def inner(*arg, **kwargs): print("outer1") return func(*arg, **kwargs) return inner def outer2(func): def inner(*arg, **kwargs): print("outer2") return func(*arg, **kwargs) return inner @outer1 @outer2 def a1(): print("a1") a1() # 结果 outer1 outer2 a1
四、类装饰器
使用类装饰器可以实现带参数装饰器的效果,但实现的更加优雅简洁,而且可以通过继承来灵活的扩展.
1、类装饰器使用
from functools import wraps class loging(object): def __init__(self, level="warn"): self.level = level def __call__(self, func): @wraps(func) def inner(*args, **kwargs): if self.level == "warn": self.notify(func) return func(*args, **kwargs) return inner def notify(self, func): # 打印日志的函数 print("{} is running".format(func.__name__)) @loging(level="warn") # 执行__call__方法 def bar(a, b): print("The sum of a and b is {}".format(a+b)) bar(1, 2)