Python中的函数装饰器高级应用:带参数装饰器、类装饰器与装饰器堆叠
字数 1562 2025-12-11 13:55:04

Python中的函数装饰器高级应用:带参数装饰器、类装饰器与装饰器堆叠


题目描述

函数装饰器是Python中用于修改或增强函数行为的工具。在掌握基础装饰器后,面试常考察其高级应用,包括带参数的装饰器类装饰器多层装饰器堆叠。这些知识点涉及闭包、函数嵌套、参数传递、描述符协议和装饰器执行顺序等核心概念。


步骤1:复习基础装饰器原理

装饰器本质上是一个可调用对象(函数或类),它接收一个函数作为参数,并返回一个新函数(或可调用对象)。装饰器语法 @decorator 是语法糖,等价于 func = decorator(func)

示例:一个简单的计时装饰器

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 执行时间: {end - start:.2f}秒")
        return result
    return wrapper

@timer
def compute(n):
    return sum(i * i for i in range(n))

compute(10000)  # 输出: compute 执行时间: 0.00秒

关键点timer 接收函数 compute,返回闭包函数 wrapperwrapper 在原函数前后添加计时逻辑。


步骤2:带参数的装饰器实现

当装饰器需要接收外部参数(如日志级别、重试次数)时,需在原有装饰器外再包装一层函数,形成三层嵌套结构。

示例:带重试机制的装饰器

def retry(max_attempts=3, delay=1):
    # 第一层:接收装饰器参数
    def decorator(func):
        # 第二层:接收被装饰函数
        def wrapper(*args, **kwargs):
            # 第三层:包装逻辑
            last_error = None
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_error = e
                    print(f"第{attempt}次失败,错误: {e}")
                    time.sleep(delay)
            raise Exception(f"重试{max_attempts}次后失败") from last_error
        return wrapper
    return decorator

@retry(max_attempts=2, delay=0.5)
def risky_call():
    import random
    if random.random() < 0.7:
        raise ValueError("随机失败")
    return "成功"

try:
    print(risky_call())
except Exception as e:
    print(f"最终失败: {e}")

执行过程

  1. @retry(max_attempts=2, delay=0.5) 先调用 retry(2, 0.5),返回 decorator 函数。
  2. decorator 接收函数 risky_call,返回 wrapper 函数。
  3. 调用 risky_call() 实际执行 wrapper(),实现重试逻辑。

步骤3:类装饰器实现

类装饰器利用类的 __init____call__ 方法实现装饰逻辑。__init__ 接收被装饰函数,__call__ 使得实例可调用,替代装饰后的函数。

示例:记录函数调用次数的类装饰器

class CallCounter:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} 被调用第{self.count}次")
        return self.func(*args, **kwargs)

@CallCounter
def greet(name):
    return f"Hello, {name}"

print(greet("Alice"))  # 输出: greet 被调用第1次 → Hello, Alice
print(greet("Bob"))    # 输出: greet 被调用第2次 → Hello, Bob

关键点CallCounter 实例化时保存原函数,调用实例时触发 __call__ 方法,实现装饰逻辑。类装饰器相比函数装饰器更易于维护状态(如 count)。


步骤4:多层装饰器堆叠与执行顺序

多个装饰器可以堆叠在一个函数上,执行顺序为从下到上(或从内到外),但调用时的执行顺序是从上到下

示例:堆叠装饰器

def decorator_a(func):
    def wrapper(*args, **kwargs):
        print("装饰器A - 前")
        result = func(*args, **kwargs)
        print("装饰器A - 后")
        return result
    return wrapper

def decorator_b(func):
    def wrapper(*args, **kwargs):
        print("装饰器B - 前")
        result = func(*args, **kwargs)
        print("装饰器B - 后")
        return result
    return wrapper

@decorator_a
@decorator_b
def target():
    print("目标函数执行")

target()

输出

装饰器A - 前
装饰器B - 前
目标函数执行
装饰器B - 后
装饰器A - 后

执行过程

  1. 装饰顺序:target = decorator_a(decorator_b(target)),即先应用 decorator_b,再应用 decorator_a
  2. 调用时:从最外层的 decorator_a.wrapper 开始执行,依次向内层传递,最后执行原函数,再向外层返回。

步骤5:装饰器常见问题与解决方案

  • 问题1:被装饰函数的元信息(如 __name____doc__)丢失
    解决:使用 functools.wraps 装饰内部函数,将原函数的元信息复制到包装函数。

    from functools import wraps
    def my_decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    
  • 问题2:装饰器在类方法上失效
    原因:类方法的第一个参数是 self,但装饰器可能未正确处理。
    解决:在装饰器内部使用 *args, **kwargs 通用参数传递,或使用 functools.wraps 保持方法绑定。

  • 问题3:装饰器调试困难
    建议:为装饰器添加日志,或使用 inspect 模块打印调用信息。


总结

  • 带参数装饰器:三层嵌套函数,外层接收装饰器参数,中层接收被装饰函数,内层实现装饰逻辑。
  • 类装饰器:通过 __init____call__ 实现,便于维护状态。
  • 装饰器堆叠:装饰顺序从下到上,执行顺序从上到下,类似洋葱模型。
  • 最佳实践:使用 functools.wraps 保留元信息,注意装饰器对性能的影响。

这些高级装饰器技巧广泛应用于Web框架(如Flask路由)、测试框架(如pytest夹具)、日志记录和性能监控等场景。

Python中的函数装饰器高级应用:带参数装饰器、类装饰器与装饰器堆叠 题目描述 函数装饰器是Python中用于修改或增强函数行为的工具。在掌握基础装饰器后,面试常考察其高级应用,包括 带参数的装饰器 、 类装饰器 和 多层装饰器堆叠 。这些知识点涉及闭包、函数嵌套、参数传递、描述符协议和装饰器执行顺序等核心概念。 步骤1:复习基础装饰器原理 装饰器本质上是一个可调用对象(函数或类),它接收一个函数作为参数,并返回一个新函数(或可调用对象)。装饰器语法 @decorator 是语法糖,等价于 func = decorator(func) 。 示例:一个简单的计时装饰器 关键点 : timer 接收函数 compute ,返回闭包函数 wrapper , wrapper 在原函数前后添加计时逻辑。 步骤2:带参数的装饰器实现 当装饰器需要接收外部参数(如日志级别、重试次数)时,需在原有装饰器外再包装一层函数,形成三层嵌套结构。 示例:带重试机制的装饰器 执行过程 : @retry(max_attempts=2, delay=0.5) 先调用 retry(2, 0.5) ,返回 decorator 函数。 decorator 接收函数 risky_call ,返回 wrapper 函数。 调用 risky_call() 实际执行 wrapper() ,实现重试逻辑。 步骤3:类装饰器实现 类装饰器利用类的 __init__ 和 __call__ 方法实现装饰逻辑。 __init__ 接收被装饰函数, __call__ 使得实例可调用,替代装饰后的函数。 示例:记录函数调用次数的类装饰器 关键点 : CallCounter 实例化时保存原函数,调用实例时触发 __call__ 方法,实现装饰逻辑。类装饰器相比函数装饰器更易于维护状态(如 count )。 步骤4:多层装饰器堆叠与执行顺序 多个装饰器可以堆叠在一个函数上,执行顺序为 从下到上 (或从内到外),但调用时的执行顺序是 从上到下 。 示例:堆叠装饰器 输出 : 执行过程 : 装饰顺序: target = decorator_a(decorator_b(target)) ,即先应用 decorator_b ,再应用 decorator_a 。 调用时:从最外层的 decorator_a.wrapper 开始执行,依次向内层传递,最后执行原函数,再向外层返回。 步骤5:装饰器常见问题与解决方案 问题1:被装饰函数的元信息(如 __name__ 、 __doc__ )丢失 解决 :使用 functools.wraps 装饰内部函数,将原函数的元信息复制到包装函数。 问题2:装饰器在类方法上失效 原因 :类方法的第一个参数是 self ,但装饰器可能未正确处理。 解决 :在装饰器内部使用 *args, **kwargs 通用参数传递,或使用 functools.wraps 保持方法绑定。 问题3:装饰器调试困难 建议 :为装饰器添加日志,或使用 inspect 模块打印调用信息。 总结 带参数装饰器 :三层嵌套函数,外层接收装饰器参数,中层接收被装饰函数,内层实现装饰逻辑。 类装饰器 :通过 __init__ 和 __call__ 实现,便于维护状态。 装饰器堆叠 :装饰顺序从下到上,执行顺序从上到下,类似洋葱模型。 最佳实践 :使用 functools.wraps 保留元信息,注意装饰器对性能的影响。 这些高级装饰器技巧广泛应用于Web框架(如Flask路由)、测试框架(如pytest夹具)、日志记录和性能监控等场景。