装饰器的本质

last modify
  • 装饰器(不带参数的装饰器)从实现上看,就是一个“输入和输出都是函数对象”的函数;

  • 带参数的装饰器实际上就是一个返回普通装饰器的函数(可以接受任意参数);

  • 装饰器语法实际上就是以下两种调用形式的语法糖:

    # 不带参数
    func = decorator(func)
    # 带参数
    func = decorator(*args)(func)
  • 类装饰器同理,就是一个“输入和输出都是类对象”的函数,也是类似的语法糖;

    # 不带参数
    clas = decorator(clas)
    # 带参数
    clas = decorator(*args)(clas)
  • 装饰器类:具有装饰器功能的类,通过重载 __call__ 实现;


示例 1:装饰器(不带参数)

  1. 定义装饰器

    def dn(fn):  # 输入是一个函数
        """一个简单的装饰器,打印被装饰函数的结果"""
        
        def wrapper(*args, **kwargs):
            ret = fn(*args, **kwargs)
            print(f'Result: {ret}')
            return ret  # 返回原函数的结果
            
        return wrapper  # 返回是一个函数
  2. 调用装饰器

    @dn
    def func(a, b):
        return a + b
    
    ret = func(1, 2)  # Result: 3
    print(ret)  # 3
  3. 等价行为

    def func(a, b):
        return a + b
    
    func = dn(func)
    
    ret = func(1, 2)  # Result: 3
    print(ret)  # 3

示例 2:带参数的装饰器

  1. 定义装饰器

    def dn_pro(prefix):  # 普通函数,可以传入任意参数
        """自定义打印前缀"""
    
        def dn(fn):
            """一个简单的装饰器,打印被装饰函数的结果"""
    
            def wrapper(*args, **kwargs):
                ret = fn(*args, **kwargs)
                print(f'{prefix}: {ret}')
                return ret  # 返回原函数的结果
    
            return wrapper
    
        return dn  # 返回一个装饰器
  2. 调用装饰器

    @dn_pro('ret')
    def func(a, b):
        """"""
        return a + b
  3. 等价行为

    def func(a, b):
        return a + b
    
    func = dn_pro('ret')(func)
    
    ret = func(1, 2)  # ret: 3
    print(ret)  # 3

示例 3functools.wraps 的作用

  • 在示例 2 中,打印 func.__name__ 的结果是 wrapper; 这是因为经过装饰器后,func 实际上已经指向了另一个函数;

  • 这时可以通过 functools.wraps 装饰器保留原函数的相关属性,用法如下:

    functools.wraps 本身也是一个带参数的装饰器;

import functools  # 导入 functools

def dn_pro(prefix):  # 普通函数,可以传入任意参数
    """自定义打印前缀"""

    def dn(fn):
        """一个简单的装饰器,打印被装饰函数的结果"""

        @functools.wraps(fn)  # 保留 fn 的相关属性
        def wrapper(*args, **kwargs):
            ret = fn(*args, **kwargs)
            print(f'{prefix}: {ret}')
            return ret  # 返回原函数的结果

        return wrapper

    return dn  # 返回一个装饰器

@dn_pro('ret')
def func(a, b):
    return a + b

print(func.__name__)  # func

示例 4:类装饰器

  • 类装饰器就是一个“输入和输出都是类对象”的函数;

  1. 定义一个类装饰器:给装饰的类添加一个 printf 函数

    def dn(cls):
        def printf(self):
            print(self.__dict__)
    
        # cls.printf = printf
        setattr(cls, 'printf', printf)
        return cls
  2. 调用装饰器

    @dn
    class A:
    
        def __init__(self, a, b):
            self.a = a
            self.b = b
    
    
    a = A(1, 2)
    a.printf()  # {'a': 1, 'b': 2}
  3. 等价行为

    class A:
    
        def __init__(self, a, b):
            self.a = a
            self.b = b
    
    
    A = dn(A)
    
    a = A(1, 2)
    a.printf()  # {'a': 1, 'b': 2}

示例 5:装饰器类(不带参数)

  • 装饰器类:具有装饰器功能的类,通过重载 __call__ 实现;

  • 需要注意的是,该场景下不能使用 functools.wraps,而应该使用 functools.update_wrapper,详见示例;

  1. 定义装饰器类

    class Dn:
    
        def __init__(self, fn):
            functools.update_wrapper(self, fn)  # !
            self.fn = fn
    
        def __call__(self, *args, **kwargs):
            ret = self.fn(*args, **kwargs)
            print(f'Result:', ret)
            return ret
  2. 调用装饰器类

    @Dn
    def func(a, b):
        return a + b
    
    ret = func(1, 2)  # Result: 3
    print(ret)  # 3
    print(func.__name__)  # func
  3. 等价行为

    def func(a, b):
        return a + b
    
    func = Dn(func)
    
    ret = func(1, 2)  # Result: 3
    print(ret)  # 3
    print(func.__name__)  # func

示例 6:带参数的装饰器类

  1. 定义装饰器类

    class Dn:
    
        def __init__(self, prefix):
            self.prefix = prefix
    
        def __call__(self, fn):
    
            @functools.wraps(fn)
            def wrapper(*args, **kwargs):
                ret = fn(*args, **kwargs)
                print(f'{self.prefix}:', ret)
                return ret
    
            return wrapper
  2. 调用装饰器类

    @Dn('ret')
    def func(a, b):
        return a + b
    
    ret = func(1, 2)  # ret: 3
    print(ret)  # 3
    print(func.__name__)  # func
  3. 等价行为

    def func(a, b):
        return a + b
    
    func = Dn('ret')(func)
    
    ret = func(1, 2)  # ret: 3
    print(ret)  # 3
    print(func.__name__)  # func

Last updated