带参数的装饰器
需要再多套一层函数:
from functools import wraps
def repeat(times): # 接收装饰器参数
def decorator(func): # 接收函数
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def hello():
print("hi")
hello()
# hi
# hi
# hi
记忆口诀:没参数的装饰器 = 一层;有参数的 = 两层。
多层装饰器堆叠
@log
@timer
def slow_add(a, b):
return a + b
# 等价于
slow_add = log(timer(slow_add))
# 调用顺序:log 在外、timer 在内
自下而上"包",自上而下"调"——记住这条规律。
类装饰器:用类当装饰器
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"第 {self.count} 次调用")
return self.func(*args, **kwargs)
@CountCalls
def hello():
print("hi")
hello() # 第 1 次调用 → hi
hello() # 第 2 次调用 → hi
print(hello.count) # 2
类装饰器适合需要状态的场景(计数、缓存、限流)。
装饰器装饰类
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 Point:
def __init__(self, x, y):
self.x = x
self.y = y
print(Point(3, 4)) # Point(x=3, y=4)
dataclass 内部就是用类似机制做的。
实用装饰器:限流
import time
from functools import wraps
def throttle(seconds):
def decorator(func):
last_call = [0]
@wraps(func)
def wrapper(*args, **kwargs):
now = time.time()
if now - last_call[0] < seconds:
print("⚠ 太频繁")
return None
last_call[0] = now
return func(*args, **kwargs)
return wrapper
return decorator
@throttle(seconds=2)
def click():
print("clicked")
click()
click() # ⚠ 太频繁
实用装饰器:重试
import time
from functools import wraps
def retry(attempts=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for i in range(attempts):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"失败 {i+1}/{attempts}: {e}")
time.sleep(delay)
raise
return wrapper
return decorator
@retry(attempts=3, delay=2)
def fetch():
...
装饰器调试技巧
装饰过的函数报错时 traceback 看起来很乱——加 @wraps 至少能保留函数名。要更深入查:"去装饰器跑一次原函数"看错是不是装饰器的锅。
下一篇讲迭代器协议——for 循环背后的机制。