decorator - yaokun123/php-wiki GitHub Wiki

装饰器

一、装饰器是什么?为什么要使用装饰器?

1.1 本质定义

装饰器是接收函数并返回新函数的[高阶函数]

它能够通过 @语法糖在不改变原函数代码的前提下,给原函数添加新的功能。

1.2 核心原理

利用闭包(内层函数引用外层函数变量)和函数作为对象(一等公民)的特性,实现“包装器”逻辑。

1.3 语法糖本质

“语法糖”是一种编程特性,它能将某些代码操作以简洁且优雅的形式呈现。
例如,在装饰器使用场景中,@decorator 作为 func = decorator(func) 的简写形式,
通过直接在函数定义上方使用 @ 符号,就能便捷地绑定装饰器,这里的 @ 便是[语法糖]

1.4 为什么要使用装饰器

使用装饰器主要有以下好处:

1、代码复用:多个函数需要相同额外功能时,封装成装饰器避免代码重复。

2、代码简洁:分离额外功能与核心逻辑,原函数专注于核心功能的实现。

3、易于维护:修改额外功能时只需调整装饰器,无需逐个修改被装饰函数。

二、装饰器如何定义及使用

2.1 装饰器使用“三步走”

1、定义装饰器函数:接收目标函数为参数,返回新的包装函数。

2、定义原函数:编写需增强功能的核心函数。

3、应用装饰器:在原函数定义前使用 @装饰器名,等价于 原函数 = 装饰器(原函数)。

2.2 装饰器的使用场景

1、计时:记录函数执行时间(使用 time.time() 计算时间差)。

2、权限校验:根据参数校验用户角色,抛出异常(如 PermissionError)。

3、类型检查:通过函数注解 @enforce_types 验证参数类型(利用 func.__annotations__)。

4、返回值增强:修改原函数返回值(如自动加固定值)。

2.3 装饰器的常见错误

1、错误调用函数:@decorator 不是 @decorator(),装饰器直接绑定函数而非调用

2、参数传递错误:未使用*args, **kwargs导致参数丢失,需在包装函数中兼容任意参数

3、循环导入:装饰器和被装饰函数相互引用时拆分代码,避免模块间循环依赖

三、基本使用

装饰器函数在模块加载时立即执行(仅执行一次),用于绑定被装饰函数,而 wrapper 函数在每次调用被装饰函数时执行。

3.1 无参数装饰器 (基本用法)

通过闭包定义内层包装函数,在调用原函数前后添加逻辑(如计时),返回包装函数。

import time 
   
def timer(func):  
    def wrapper():  
        start = time.time()  # 记录开始时间
        result = func()       # 执行原函数
        print(f"耗时:{time.time()-start:.2f}秒")  # 计算耗时
        return result         # 显式返回原函数结果
    return wrapper  

@timer  
def slow_func():  
    time.sleep(1)  

slow_func()  # 输出执行时间  

3.2 保留原函数元信息

使用 functools.wraps 装饰包装函数,避免原函数的名称、文档字符串等元信息被覆盖。

from functools import wraps  

def decorator(func):  
    @wraps(func)  # 保留原函数元信息(函数名、注释等)  
    def wrapper(*args, **kwargs):  
        return func(*args, **kwargs)  
    return wrapper  

@decorator  
def example():  
    """示例函数"""  
    pass  

print(example.__name__)  # 输出"example"(而非默认的"wrapper")  

3.3 处理带参数的函数

包装函数使用 *args, **kwargs 接收任意参数,确保装饰器兼容不同参数形式的函数。

from functools import wraps  

def logger(func):  
    @wraps(func)  # 保留原函数元信息(如函数名、文档字符串)  
    def wrapper(*args, **kwargs):  
        print(f"调用函数:{func.__name__},参数:{args}, {kwargs}")  
        return func(*args, **kwargs)  # 传递参数并返回原函数结果  
    return wrapper  

@logger  
def add(a, b):  
    return a + b  

print(add(2, b=3))  # 输出参数和结果  

3.4 带参数的装饰器

通过三层嵌套函数,外层接收装饰器参数,中层绑定目标函数,内层包装函数实现参数化逻辑(如重复执行函数n次)。

from functools import wraps

def repeat(n):    # 第一层:装饰器工厂,接收参数
    def decorator(func):    # 第二层:真正的装饰器
        @wraps(func)
        def wrapper(*args, **kwargs):    # 第三层:wrapper 函数
            results = []
            # 循环n次调用原函数
            for _ in range(n):
                results.append(func(*args, **kwargs))
            return results  # 返回最终结果而非None
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print(f"Hello {name}")  # 打印欢迎语句
    return f"Hello {name}"  # 返回欢迎语句


greet("小帅")  # 输出3次"Hello 小帅"
print(greet("小帅"))  # ['Hello 小帅', 'Hello 小帅', 'Hello 小帅']

3.5 多层装饰器

装饰器绑定:my_func = decorator1(decorator2(my_func))

调用 my_func() 时的实际执行顺序:

  • 进入 decorator1 的 wrapper1
  • wrapper1 中执行权限校验逻辑
  • wrapper1 调用 decorator2 的 wrapper2
  • wrapper2 调用原始函数 my_func
  • wrapper2 中执行日志记录逻辑
  • 以上执行完毕后,以相反顺序返回
  • 离开 decorator2 的 wrapper2
  • 离开 decorator1 的 wrapper1
def decorator1(func):
    def wrapper1(*args, **kwargs):
        print("进入外层装饰器 wrapper1")
        print("校验权限...")
        # 这里可加入权限校验逻辑
        result = func(*args, **kwargs)  # 调用 decorator2 的 wrapper2
        print("离开外层装饰器 wrapper1")
        return result

    return wrapper1


def decorator2(func):
    def wrapper2(*args, **kwargs):
        print("进入内层装饰器 wrapper2")
        result = func(*args, **kwargs)  # 调用原始函数
        print("记录操作日志...")
        # 这里可加入记录操作逻辑
        print("离开内层装饰器 wrapper2")
        return result

    return wrapper2


@decorator1
@decorator2
def my_func():
    print("执行原始函数")


# 调用被装饰后的函数
my_func()

3.6 类装饰器

装饰器不一定是函数,也可以用类来实现。装饰器函数其实是一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。

在python中,一般callable对象都是函数,但是也有例外。比如只要某个对象重写了call方法,那么这个对象就是callable的。

类装饰器通过 _call_ 方法让对象可以像函数一样被调用,适合需要保存状态的场景。

当创建一个对象后,直接去执行这个对象,那么是会抛出异常的,因为他不是callable,无法直接执行。
但进行修改后,就可以直接执行调用了,如下

class Test(object):
    def __call__(self, *args, **kwargs):
        print('call called')


t = Test()
print(t())

实现无参数的类装饰器

无参数的类装饰器直接接收被装饰函数作为参数,通过 __call__ 方法实现装饰逻辑:

import time
 
class Timer:
    def __init__(self, func):
        self.func = func  # 保存被装饰的函数
    
    def __call__(self, *args, **kwargs):
        start_time = time.time()
        result = self.func(*args, **kwargs)  # 调用原函数
        end_time = time.time()
        print(f"{self.func.__name__} 执行时间: {end_time - start_time:.4f}秒")
        return result
 
@Timer
def slow_function():
    time.sleep(1)
    print("函数执行完毕")
 
slow_function()  # 实际调用的是 Timer 实例的 __call__ 方法

实现带参数的类装饰器

带参数的类装饰器需要在 __init__ 中接收参数,并在 __call__ 中返回包装函数

class Repeat:
    def __init__(self, num_times):
        self.num_times = num_times  # 接收装饰器参数
    
    def __call__(self, func):
        # 这里返回一个包装函数
        def wrapper(*args, **kwargs):
            for _ in range(self.num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
 
@Repeat(num_times=3)
def greet(name):
    print(f"Hello, {name}!")
 
greet("Bob")

类装饰器装饰类

类装饰器可以动态修改类的行为,例如添加方法或属性:

类装饰器是一个函数,它接受一个类作为参数,并返回一个新的类或对原类进行修改。

def add_method(cls):
    # 为类添加一个新方法
    def new_method(self):
        return f"这是 {self.__class__.__name__} 的新方法"
    
    # 给类绑定新方法
    cls.new_method = new_method
    return cls
 
@add_method
class MyClass:
    pass
 
obj = MyClass()
print(obj.new_method())  # 输出: 这是 MyClass 的新方法