直接回答:装饰器就是语法糖:@deco 加在函数上等价于 func = deco(func)——接收函数、返回(通常是包装的)函数。带参数的装饰器是"返回装饰器的函数":@retry(3) 先调用 retry(3) 得到真正的装饰器再包函数,所以三层嵌套:参数层、函数层、参数包装层。functools.wraps 把原函数的 __name____doc__、签名元数据复制到包装函数上,没有它,被装饰函数的身份信息全部变成 wrapper,日志、调试、文档和基于内省的路由框架都会错乱。

展开解析:进阶认知:装饰在定义时执行而非调用时——模块导入即生效,这既是注册模式(Flask 路由、@lru_cache)的基础,也意味着装饰器里的重逻辑会拖慢启动;装饰器叠加时自下而上应用。实现形态上类装饰器(__call__)适合需要状态的装饰(计数、缓存),函数形态适合纯包装。常见生产级例子:重试(注意与幂等性配合)、计时埋点、权限校验、参数校验。一个隐蔽坑:装饰器吞掉原函数签名后,*args, **kwargs 的 wrapper 让类型检查和 IDE 提示失效,现代写法用 ParamSpec(PEP 612)标注保真签名。记住装饰器是组合优于继承的经典体现——横切逻辑(日志、鉴权、缓存)以可插拔方式附着。

追问方向:类装饰器与函数装饰器的取舍?ParamSpec 如何保真类型?装饰器与 AOP 的关系?

(约 440 字)