基于FastApi框架测试平台(14)-装饰器复习

421 阅读3分钟

前言

这节先复习一下装饰器,因为接下来针对一些复杂接口的响应统一使用装饰器进行处理。

函数核心

  • 函数是对象。可以把函数赋予变量

    def get_message(message):
        print(message)
    ​
    a = get_message
    a("hello python")
    
  • 函数可以当作参数,传给另一个函数

    def get_message(message):
        print(message)
    ​
    def call(func, message):
        func(message)
    ​
    call(get_message, "hello python")
    
  • 函数中可以定义函数,函数的嵌套

    def func(message):
        def get_message(message):
            print(message)
        return get_message(message)
    func("hello python")
    
  • 函数的返回值可以是函数对象,闭包

    def func():
        def get_message(message):
            print(message)
        return get_message
    ​
    fu = func()
    fu("hello python")
    

简单的装饰器

def my_decorator(func):
    def wrapper():
        print('我是装饰器')
        func()
    return wrapper
​
def greet():
    print('hello python')
​
gre = my_decorator(greet)
gre()

变量gre指向内部函数wrapper,wrapper中又调用原函数greet。把真正需要执行的函数greet包裹在my_decorator中,并且改变了它的行为,但原函数greet不变。

上述代码,简单优雅的表示

def my_decorator(func):
    def wrapper():
        print('我是装饰器')
        func()
    return wrapper
​
@my_decorator
def greet():
    print('hello python')
​
greet()

这两段代码可以看出,@my_decorator等价于gre = my_decorator(greet)

带有参数的装饰器

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper

带有自定义参数的装饰器

案例:定义一个参数,表示装饰器内部函数被执行的次数

def repeat(num):
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(num):
                print('我是装饰器')
                func(*args, **kwargs)
        return wrapper
    return my_decorator
​
​
@repeat(4)
def greet(message):
    print(message)
​
greet('hello python')
'''
我是装饰器
hello python
我是装饰器
hello python
我是装饰器
hello python
我是装饰器
hello python
'''

原函数变了,怎么处理

print(greet.__name__) # wrapperhelp(greet)
'''
Help on function wrapper in module __main__:
​
wrapper(*args, **kwargs)
'''

输出结果可以看到,函数被装饰后,元信息改变了,从greet变为了wrapper

为了保留原函数的元信息,我们需要使用内置的装饰器@functools.wrap

import functools
def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('我是装饰器')
        func(*args, **kwargs)
    return wrapper
​
​
@my_decorator
def greet(message):
    print(message)
​
print(greet.__name__) # greethelp(greet)
'''
Help on function greet in module __main__:
​
greet(message)
'''

不改变元信息有啥意义呢?我觉得如果多个函数被同一装饰器装饰,除了问题,就能找到哪个函数有问题

类装饰器

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

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()
'''
num of calls is: 1
hello world
'''

当我们使用 @Count 修饰函数 example 时,相当于将函数 example 作为参数传入 Count 类中构造一个对象,并将该对象重定义为新的 example 函数。由于 Count 类实现了 __call__ 方法,因此这个新的 example 函数在被调用时实际上是调用了 Count 类的 __call__ 方法。

__call__ 方法中,使用 self.num_calls 访问和修改了 Count 类的实例变量 num_calls 的值。由于在每次调用新的 example 函数时,都会执行一次 Count 类的 __call__ 方法,因此 num_calls 的值会被持续地累加。

换句话说,num_callsCount 类的实例变量,而不是 example 函数的实例变量,因此其值可以被多次调用 example 函数所共享和修改。

装饰器的嵌套

Python支持多个装饰器

@decorator1
@decorator2
@decorator3
def func():
    ...

多个装饰器执行顺序是从里到外,decorator1(decorator2(decorator3(func)))

import functools
​
def my_decorator1(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('装饰器1')
        func(*args, **kwargs)
    return wrapper
​
​
def my_decorator2(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('装饰器2')
        func(*args, **kwargs)
    return wrapper
​
​
@my_decorator1
@my_decorator2
def greet(message):
    print(message)
​
​
greet('hello python')
​
'''
装饰器1
装饰器2
hello python
'''

会先执行 my_decorator2 函数,返回一个 wrapper 函数作为装饰器;接着,再将 wrapper 函数作为参数传入 my_decorator1 函数中,生成一个新的 wrapper 函数,该函数在调用 greet 函数之前会打印 "装饰器1",然后再调用 my_decorator2 中的 wrapper 函数,打印 "装饰器2",最后才会执行 greet 函数

后续

到这里了解了装饰器,后续我们将实战,看如何使用装饰器解决响应复杂数据的问题。