1.装饰器简介
python的装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
简单的说装饰器就是一个用来返回函数的函数。
它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,
有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
认识装饰器之前,需要明确的事情:
# 装饰器在不改变函数内部的基础上,可以统一添加某些功能,让函数在执行之前或之后做一些操作
# 定义完函数未调用,函数内部不执行
# 函数名(不加括号),代指函数整体;加括号:执行函数
# 函数名可以当作参数传递
# 只要函数用上装饰器,那么函数就会被重新定义为装饰器的内层函数
2.为什么使用装饰器
首先有个函数:
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
3.装饰器的使用
3-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()
3-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-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)
3-4 functools.wraps
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解决这个问题。
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
3-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
4.类装饰器
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)