人人都会AI编程

带参数装饰器、类装饰器、多层装饰器

更新时间:2026-07-12

在掌握了基础装饰器(不带参数的函数装饰器)之后,实际工作中经常会遇到更灵活的装饰需求。下面三种进阶用法,能让你更精准地控制装饰行为。

带参数装饰器

普通装饰器直接接收被装饰函数作为参数,而带参数装饰器则允许在装饰时传入额外的配置参数,用来调整装饰器的行为(比如控制重试次数、设置日志级别等)。

实现原理:本质上是“再加一层函数包装”。外层函数接收装饰参数,返回一个真正的装饰器(内层函数),该装饰器再接收被装饰函数并对其加工。

import functools
import time

def retry(max_tries=3, delay=1):
    """带参数的装饰器:函数失败时自动重试"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_tries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"第 {attempt+1} 次尝试失败: {e}")
                    if attempt == max_tries - 1:
                        raise
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_tries=4, delay=2)   # 可以自定义重试次数和间隔
def fetch_data(url):
    # 模拟可能失败的请求
    ...

@retry(max_tries=4) 实际上执行的是 retry(max_tries=4),它返回 decorator,然后 decorator 再装饰 fetch_data。这就是三层函数的妙用。

实用场景:API 调用重试、权限校验中需要传入不同角色、为函数注册时打标签等。

类装饰器

除了用函数实现装饰器,也可以用类来实现。类装饰器利用类的 call 方法,让实例可调用,完成对函数的包装。它的优势在于可以将状态保存在实例属性中,更清晰地组织复杂逻辑。

import functools

class CountCalls:
    """统计函数被调用次数的装饰器"""
    def __init__(self, func):
        functools.update_wrapper(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)

@CountCalls
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")   # 输出调用次数
greet("Bob")

执行过程:@CountCalls 相当于 greet = CountCalls(greet),此时 greet 变成了 CountCalls 的实例,但调用 greet("Alice") 会触发实例的 call 方法,从而在原来函数执行前后加上统计逻辑。

如果类装饰器也需要带参数,可以在 init 中接收配置参数,并在 call 中返回包装后的函数(此时类装饰器本身更像一个工厂)。

多层装饰器

一个函数可以被多个装饰器同时修饰。多层装饰器的执行顺序遵循“洋葱模型”:最靠近函数定义的装饰器最先应用,执行时则从外到内依次进入,再从内到外依次返回。

@decorator1
@decorator2
def func():
    pass

# 等价于 func = decorator1(decorator2(func))

执行流程示例

def bold(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return "<b>" + func(*args, **kwargs) + "</b>"
    return wrapper

def italic(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return "<i>" + func(*args, **kwargs) + "</i>"
    return wrapper

@bold
@italic
def hello():
    return "Hello"

print(hello())   # <b><i>Hello</i></b>
  • 装饰时:先 italic 装饰 hello,返回一个 wrapper;然后 bold 再装饰这个 wrapper
  • 调用时:先执行 boldwrapper(添加 <b>),其中调用 func 实际是 italicwrapper(添加 <i>),最终返回的顺序是从外到内包起来。

实际应用:权限校验 + 日志记录、缓存 + 异常重试、多种格式化处理等。注意保留好 @functools.wraps,否则多层装饰下原函数信息会丢失得更严重。

掌握这三种进阶装饰器,你可以将横切关注点(如日志、认证、重试、计时)灵活地从业务代码中剥离出来,让代码复用性和可维护性再上一个台阶。