装饰器本质是"接收一个函数、返回一个新函数"的可调用对象,@decorator 只是 func = decorator(func) 的语法糖。利用闭包,新函数在原函数执行前后插入额外逻辑(日志、计时、鉴权、缓存等),而不修改原函数代码。
带参数的装饰器需要再包一层:外层接收参数,中层接收函数,内层是包装函数——即"装饰器工厂"。
import functools, time
def retry(times=3, delay=1):
def decorator(func):
@functools.wraps(func) # 保留原函数元信息
def wrapper(*args, **kwargs):
for i in range(times):
try:
return func(*args, **kwargs)
except Exception:
if i == times - 1:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(times=3, delay=0.5)
def fetch(): ...
关键点:@retry(...) 先执行 retry(3, 0.5) 得到真正的装饰器,再作用到 fetch 上,等价于 fetch = retry(3, 0.5)(fetch)。易错点:忘记 functools.wraps 会导致被装饰函数的 __name__、__doc__ 丢失,影响调试和框架自省。追问方向:类装饰器、装饰器执行顺序、如何用装饰器实现 Flask 路由。