Python装饰器

发布于 2021 年 08 月 04 日 | 更新于 2021 年 08 月 17 日

概述

装饰器本质就是一个语法糖@,执行固定的操作:用decorator(obj)的返回值替换obj这个名字,即obj = decorator(obj)扩展功能是通过python的其他特性(如闭包)实现的。

规则

流程

  1. 调用装饰器,参数为被装饰者decorator(obj),执行装饰器内部代码
  2. 返回装饰后的对象,替换被装饰者,使obj指向装饰器返回的对象

分别以装饰器被装饰者的视角来看:

用途

  1. 不修改函数调用方式的前提下,在函数的前后添加功能(如:计算函数执行时间)reference(内容有错误)
  2. 注册器,往对象中注册函数,通过字符串调用 reference
  3. 单例类/单例模式:仅有一个实例对象的类

装饰器代码的执行时机

在被装饰对象定义或初始化完成后,就会进行装饰(执行装饰器),而不是等到调用被装饰对象时才进行装饰

def decorator(obj):  # 用函数来定义装饰器
    name = hasattr(obj, '__qualname__') and obj.__qualname__ or obj
    print(f'call decorator, obj is {name}')

    def warpper(*args, **kwargs):  # (*args, **kwargs) 保持参数一致的最简单的方法
        print('call decorator->warpper')
        return obj(*args, **kwargs)

    return warpper

class ClassDecorator:  # 用类来定义装饰器
    def __init__(self, obj) -> None:
        name = hasattr(obj, '__qualname__') and obj.__qualname__ or obj
        print(f'call ClassDecorator, obj is {name}')
        self.obj = obj

    def __call__(self, *args, **kwargs):
        print('call ClassDecorator->__call__')
        return self.obj(*args, **kwargs)

# 定义 my_fun1 然后执行 decorator(my_fun1)
@decorator
def my_fun1():  # 用于装饰函数 my_fun1 = decorator(my_fun1) 将 my_fun1 替换为包装后的函数
    print('call my_fun1')

# 定义 my_fun2 然后执行 ClassDecorator(my_fun2)
@ClassDecorator
def my_fun2():
    print('call my_fun2')

# 初始化 A 然后执行 decorator(A)
@decorator
class A(object):  # 用于装饰类 A = decorator(A)
    print('init class A begin...')

    def __init__(self) -> None:
        print('call A.__init__')

    @decorator  # 返回函数,可以变成bound method
    def instance_fun1(self):
        print('call instance_fun1')

    @decorator  # 把对象的__call__方法包装成函数,就能变成bound method了
    @ClassDecorator  # 返回对象,不能变成bound method
    def instance_fun2(self):
        print('call instance_fun2')

    print('init class A end...')

print('before call my_fun1')
my_fun1()
print('before call my_fun2')
my_fun2()
a = A()
a.instance_fun1()

运行结果如下:

call decorator, obj is my_fun1
call ClassDecorator, obj is my_fun2
init class A begin...
call decorator, obj is A.instance_fun1
call ClassDecorator, obj is A.instance_fun2
call decorator, obj is <__main__.ClassDecorator object at 0x7fc79d1bf850>
init class A end...
call decorator, obj is A
before call my_fun1
call decorator->warpper
call my_fun1
before call my_fun2
call ClassDecorator->__call__
call my_fun2
call decorator->warpper
call A.__init__
call decorator->warpper
call instance_fun1

总结

调用被装饰者obj时,可以把obj看成是decorator返回值

参考

  1. Python 小技巧 —— 用类写装饰器

  2. reference