6月2日 01:33

Python 装饰器高级用法有哪些?带参数装饰器、类装饰器和 functools.wraps 详解

装饰器的高级用法围绕三个问题:怎么传参数、怎么保持被装饰函数的元信息、什么时候用类而不是函数写装饰器。

带参数的装饰器

普通装饰器只能装饰函数,不能接收额外参数。需要参数时,加一层嵌套:

python
def retry(max_attempts=3, delay=1): def decorator(func): def wrapper(*args, **kwargs): for attempt in range(max_attempts): try: return func(*args, **kwargs) except Exception as e: if attempt == max_attempts - 1: raise time.sleep(delay) return wrapper return decorator @retry(max_attempts=5, delay=2) def call_api(): ...

@retry(5, 2) 先调用 retry(5, 2) 返回 decorator,再 decorator(call_api) 返回 wrapper。三层嵌套是带参数装饰器的固定模式。

functools.wraps:保持函数身份

装饰器替换了原函数,导致 __name____doc__ 等元信息丢失:

python
def log(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper @log def hello(): pass print(hello.__name__) # 'wrapper' 而不是 'hello'

@wraps(func) 把原函数的元信息复制到 wrapper:

python
from functools import wraps def log(func): @wraps(func) def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper print(hello.__name__) # 'hello'

@wraps 必须加——调试时看到 wrapper 而不是实际函数名,排查问题会非常痛苦。

类装饰器

用类写装饰器,适合需要维护状态的场景:

python
class CountCalls: def __init__(self, func): self.func = func self.count = 0 wraps(func)(self) # 保持元信息 def __call__(self, *args, **kwargs): self.count += 1 print(f"{self.func.__name__} called {self.count} times") return self.func(*args, **kwargs) @CountCalls def say_hello(): print("Hello") say_hello() # say_hello called 1 times; Hello say_hello() # say_hello called 2 times; Hello

__init__ 接收被装饰的函数,__call__ 每次调用时执行。self.count 跨调用持久化——函数写装饰器用闭包变量存状态,类装饰器用实例属性,语义更清晰。

装饰类

装饰器不只能装饰函数,还能装饰整个类:

python
def add_repr(cls): def __repr__(self): attrs = ', '.join(f'{k}={v!r}' for k, v in self.__dict__.items()) return f'{cls.__name__}({attrs})' cls.__repr__ = __repr__ return cls @add_repr class User: def __init__(self, name, age): self.name = name self.age = age print(User('Alice', 30)) # User(name='Alice', age=30)

dataclass 的 @dataclass 就是类装饰器——自动生成 __init____repr____eq__

常见的高级装饰器模式

1. 缓存/记忆化

python
from functools import lru_cache @lru_cache(maxsize=128) def expensive(n): return sum(i ** 2 for i in range(n))

标准库自带,不用自己写。maxsize=None 无限缓存。

2. 类型检查

python
def validate(**types): def decorator(func): @wraps(func) def wrapper(**kwargs): for name, expected in types.items(): if name in kwargs and not isinstance(kwargs[name], expected): raise TypeError(f'{name} must be {expected}') return func(**kwargs) return wrapper return decorator @validate(age=int, name=str) def create_user(name, age): ...

3. 超时控制

python
import signal def timeout(seconds): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): def handler(signum, frame): raise TimeoutError(f'{func.__name__} timed out') old = signal.signal(signal.SIGALRM, handler) signal.alarm(seconds) try: return func(*args, **kwargs) finally: signal.alarm(0) signal.signal(signal.SIGALRM, old) return wrapper return decorator

追问

多个装饰器的执行顺序?

从下到上装饰,从上到下执行。@a @b def f() 等价于 a(b(f)),调用 f() 时先执行 a 的逻辑,再执行 b 的逻辑,最后执行 f

装饰器和 AOP 是什么关系?

装饰器是 Python 实现 AOP(面向切面编程)的方式。日志、权限、缓存这些"横切关注点"用装饰器统一处理,不侵入业务代码。

标签:Python