python装饰器

一、装饰器概述

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)