装饰器执行顺序自下而上,因语法糖展开为f = a(b(f)),故@b先包装、@a后包装;调试时可用print验证,嵌套时外层测的是内层加原函数总耗时。

装饰器执行顺序为什么是自下而上?
多个装饰器写在函数上方时,@decorator_a 在 @decorator_b 下方,实际执行顺序却是 decorator_b 先包装,再被 decorator_a 包装。这是 Python 语法糖展开后的必然结果:@a\n@b\ndef f(): 等价于 f = a(b(f))。
常见错误是以为“上面的装饰器先执行”,结果调试时发现日志顺序或副作用不符合预期。比如两个记录耗时的装饰器嵌套,外层 @timer 实际测的是内层装饰器加原函数的总时间,而非纯函数本身。
- 把装饰器看作“套盒子”:最靠近函数的装饰器最先被套进去(即最先执行),最外层的最后套上(但调用时最先触发)
- 如果需要控制执行流(比如某个装饰器必须最晚生效),就把它写在最上方
- 调试时可在每个装饰器内部加
print(f"enter {__name__}")和print(f"exit {__name__}")验证顺序
带参数的装饰器和无参装饰器混用会报什么错?
当写成 @auth(role="admin")\n@cache\ndef view(): 时,@auth(role="admin") 是一个调用表达式,返回真正的装饰器函数;而 @cache 是直接应用装饰器。两者可以共存,但容易因返回值类型不一致出错——比如 auth 返回了非可调用对象,或者 cache 没正确处理被装饰函数的签名。
典型错误信息:TypeError: 'function' object is not callable 或 TypeError: wrapper() takes 0 positional arguments but 1 was given。
- 确保所有装饰器最终都返回一个接受且转发
*args, **kwargs的包装函数 - 用
functools.wraps(func)保留原函数元信息,否则嵌套后inspect.signature()可能失效 - 带参数装饰器必须是三层函数结构:外层接收参数,中层接收被装饰函数,内层是实际 wrapper
如何让多个装饰器共享上下文状态?
比如一个 @log_calls 记录调用,一个 @validate 做参数校验,它们都需要访问当前请求 ID 或用户上下文。Python 中没有内置跨装饰器状态传递机制,硬编码全局变量或线程局部存储(threading.local())风险高,尤其在异步或协程场景下会错乱。
更可靠的方式是借助函数属性或显式传入上下文对象:
- 给被装饰函数动态添加属性,如
func._context = context,但需注意装饰器执行时机(是在定义时还是调用时) - 统一用一个“上下文装饰器”打头,将上下文注入
kwargs,后续装饰器从**kwargs中读取,例如@inject_context\n@validate\n@log_calls - 避免在装饰器内部修改闭包变量,不同调用之间可能相互污染
async def 函数能用普通装饰器吗?
不能直接用。普通装饰器返回的 wrapper 通常是同步函数,而 async def 函数返回的是 coroutine 对象。若用同步装饰器包裹异步函数,调用时不会自动 await,导致返回 coroutine 而不是实际结果,还可能引发 RuntimeWarning: coroutine 'xxx' was never awaited。
必须区分装饰目标:同步函数用同步 wrapper,异步函数要用 async def wrapper,且内部用 await 调用原函数。
- 检查被装饰对象是否为协程函数:用
inspect.iscoroutinefunction(func) - 不要试图在同步 wrapper 里
asyncio.run()—— 这会阻塞事件循环,且无法嵌套在已有 event loop 中 - 如果装饰器逻辑本身要异步(如查 Redis 缓存),那它必须是异步装饰器,且只用于
async def函数
*args, **kwargs 转发或 functools.wraps 缺失,就会让问题藏得又深又晚。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











