装饰器介绍

装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

一个简单的装饰器

def use_logging(func):
    def wrapper():
        print(f"{func.__name__} is running~")
        # 把 foo当作参数传递进来时,执行 func()就相当于执行 foo()
        func()
    return wrapper

def foo():
    print("hello world!")

>>>foo = use_logging(foo)   # 这条语句相当于 foo = wrapper
>>>foo()   # 执行 foo() 就相当于执行 wrapper()
foo is running~
hello world!

use_loggin 就是一个装饰器,它是一个普通的函数,它把执行真正业务逻辑的函数 func包裹其中,看起来就像 foo被 use_logging 装饰了一样,use_logging 返回的也是一个函数,这个函数的名字叫 wrapper。在这个例子中,函数进入和退出时 ,被称为一个横切面,这种编程方式被称为面向切面的编程。

@语法糖

@ 符号就是装饰器的语法糖,它放在函数开始定义的地方,这样就可以省略最后一步再次赋值的操作。

def use_logging(func):
    def wrapper():
        print(f"{func.__name__} is running~")
        return func()
    return wrapper

@use_logging
def foo():
    print("hello world!")

>>>foo()
foo is running~
hello world!

如上所示,有了 @ ,我们就可以省去 foo = use_logging(foo) 这一句了,直接调用 foo() 即可得到想要的结果。此时,foo() 函数不需要做任何修改,只需在定义的地方加上装饰器,调用的时候还是和以前一样,如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。

装饰器在 Python 使用如此方便都要归因于 Python 的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。

对带参数的函数进行装饰

def use_logging(func):
    def wrapper(x):
        print(f"{func.__name__} is running~")
        return func(x)
    return wrapper

@use_logging
def foo(x):
    print(f"hello {x}")

foo("Github!")

让装饰器带参数

装饰器的语法允许我们在调用时,提供其它参数,比如 @use_logging(level="warn"),这样,就为装饰器的编写和使用提供了更大的灵活性。比如,我们可以指定日志的等级:

def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            elif level == "info":
                logging.info("%s is running" % func.__name__)
            return func(*args)
        return wrapper
    return decorator

@use_logging(level="warn")
def foo(name='foo'):
    print("i am %s" % name)

foo()

上面的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我们使用@use_logging(level="warn") 调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。

@use_logging(level="warn")等价于 @decorator

多个装饰器的执行顺序

一个函数还可以同时定义多个装饰器,比如:

@a
@b
@c
def f ():
    pass

它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于

f = a(b(c(f)))

详情可参考:Python 装饰器执行顺序迷思