Skip to content

深入理解 Python 装饰器

前言

装饰器在 Python 工程应用非常的广泛, 比如日志,缓存等各个方面

函数装饰器

简单的装饰器

python
def simple_decorator(func):
    def wrapper():
        print("wrapper of decorator")
        func()

    return wrapper


def print_hello():
    print('hello, simple decorator')


res = simple_decorator(print_hello)
res()

#输出
wrapper of decorator
hello, simple decorator

变量 res 指向了内部函数 wrapper(), 内部函数 wrapper()又会调用原函数 print_hello(),因此,调用 res(),会先打印'wrapper of decorator',然后打印出'hello, simple decorator' 这里的函数 simple_decorator()就是一个装饰器,真正需要执行的函数 print_hello()包裹在其中, 改变了其行为,但是原函数 print_hello()不变

为了让这个装饰器看起来更简洁,更优雅的表示,Python 提供@语法糖,那么上面代码可以改成

python
def simple_decorator(func):
    def wrapper():
        print("wrapper of decorator")
        func()

    return wrapper


@simple_decorator
def print_hello():
    print('hello, simple decorator')

print_hello()

通过@语法糖就大大提高了函数的重复利用和程序的可读性

带参数的装饰器

通常,我们会把*args 和 **kwargs,作为装饰器内部函数 wrapper()的参数,*args 和**kwargs,表示接收任意数量和参数的类型

python
def simple_decorator(func):
    def wrapper(*args, **kwargs):
        print("wrapper of decorator")
        func(*args, **kwargs)

    return wrapper


@simple_decorator
def print_hello(name, age):
    print('wrapper of  decorator')
    print(f'name = {name} age = {age}')


print_hello('lcd',18)

带有自定义参数的装饰器

有时候,装饰器执行的时候,需要传入参数, 内部函数的执行跟你装饰器的参数又很大的挂钩,比如我想要定义一个参数,来表示装饰器内部函数被执行的次数,那么就可以写成下面这种形式:

python
def repeat(num):
    def my_decorator(func):
        @functools.wrap
        def wrapper(*args, **kwargs):
            for i in range(num):
                print('wrapper of decorator')
                func(*args, **kwargs)

        return wrapper

    return my_decorator


@repeat(4)
def greet(message):
    print(message)


greet('hello world')

# 输出:
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world

装饰器@functools.wrap,它会帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)。

类装饰器

类装饰器的实现主要依赖函数call(),每当调用一个类的示例的时,函数call()就会被执行一次。

python
class Count:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print('num of calls is: {}'.format(self.num_calls))
        return self.func(*args, **kwargs)


@Count
def example():
    print("hello world")


example()

example()

# 输出
num of calls is: 1
hello world
num of calls is: 2
hello world

我们定义了类 Count,初始化时传入原函数 func(),而call()函数表示让变量 num_calls 自增 1,然后打印,并且调用原函数。因此,在我们第一次调用函数 example() 时,num_calls 的值是 1,而在第二次调用时,它的值变成了 2。

装饰器嵌套

执行顺序从里到外,也就是从上到下

python
import functools


def my_decorator1(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('execute decorator1')
        func(*args, **kwargs)

    return wrapper


def my_decorator2(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('execute decorator2')
        func(*args, **kwargs)

    return wrapper


@my_decorator1
@my_decorator2
def greet(message):
    print(message)


greet('hello world')

# 输出
execute decorator1
execute decorator2
hello world

装饰器的应用场景

  1. 身份认证
python
import functools

def authenticate(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        request = args[0]
        if check_user_logged_in(request): # 如果用户处于登录状态
            return func(*args, **kwargs) # 执行函数 post_comment()
        else:
            raise Exception('Authentication failed')
    return wrapper

@authenticate
def post_comment(request, ...)
    ...
  1. 日志记录

线上测试某些函数的执行时间,那么,装饰器就是一种很常用的手段。

python
import time
import functools

def log_execution_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        res = func(*args, **kwargs)
        end = time.perf_counter()
        print('{} took {} ms'.format(func.__name__, (end - start) * 1000))
        return res

    return wrapper


@log_execution_time
def record_time():
    time.sleep(3)


record_time()

# 输出
record_time took 3004.3250000000003 ms
  1. 参数校验

输入合理性校验

python
import functools

def validation_check(input):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        ... # 检查输入是否合法

@validation_check
def neural_network_training(param1, param2, ...):
    ...
  1. 缓存

LRU cache,在 Python 中的表示形式是@lru_cache。@lru_cache 会缓存进程中的函数参数和结果,当缓存满了以后,会删除 least recenly used 的数据

总结

装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。

Released under the MIT License.