带参数装饰器本质是三层嵌套函数:最外层接收装饰器参数并返回中间层装饰器,中间层接收被装饰函数并返回内层wrapper,内层负责执行逻辑与重试等操作。
带参数的装饰器不是直接修饰函数,而是先接收参数,再返回一个真正的装饰器。整个结构是三层函数嵌套:最外层接收装饰器参数,中间层接收被装饰函数,最内层负责执行逻辑。比如 @retry(max_attempts=3),retry 是最外层函数,它拿到 max_attempts=3 后返回一个标准装饰器(中间层),这个装饰器再作用于目标函数。
以下是一个实用的带参数重试装饰器,支持指定最大尝试次数和异常类型:
(注意:实际使用中建议用 tenacity 等成熟库,此处为理解原理)
retry 接收 max_attempts 和 exceptions 参数,只运行一次(装饰时调用)dec
orator 接收被装饰的函数 func,返回内层包装函数wrapper 在每次调用时执行逻辑,捕获异常并按需重试代码示例:
def retry(max_attempts=3, exceptions=(Exception,)):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except exceptions as e:
if attempt == max_attempts - 1:
raise
print(f"Attempt {attempt + 1} failed: {e}, retrying...")
return None
return wrapper
return decorator
使用方式
@retry(max_attempts=2, exceptions=(ValueError,))
def fetch_data():
import random
if random.random() < 0.8:
raise ValueError("Network timeout")
return "success"
用类实现带参数装饰器更清晰,也更易扩展状态(如累计调用次数、耗时统计):
__init__ 接收装饰器参数(如 timeout=5)__call__ 接收被装饰函数,并返回一个闭包或绑定方法作为实际包装器示例(超时控制装饰器):
class timeout:
def __init__(self, seconds):
self.seconds = seconds
def __call__(self, func):
def wrapper(*args, **kwargs):
import signal
def timeout_handler(signum, frame):
raise TimeoutError(f"{func.__name__} timed out after {self.seconds}s")
old_handler = signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(self.seconds)
try:
result = func(*args, **kwargs)
return result
finally:
signal.alarm(0)
signal.signal(signal.SIGALRM, old_handler)
return wrapper使用
@timeout(seconds=2)
def slow_function():
import time
time.sleep(3)
return "done"
常见陷阱与调试技巧
写带参数装饰器时容易出错,关键点在于分清“装饰时”和“调用时”的执行时机:
retry(max_attempts=3) 里打印日志,会导致装饰时就输出,而非函数调用时@functools.wraps(func) 修饰内层 wrapper,否则 help()、func.__name__ 会丢失exceptions=[] 改成 exceptions=None,再在函数体内设为 (Exception,),避免跨调用污染