装饰器函数本身不必是协程,但其返回的包装函数必须是 async def 定义的协程函数,才能被 await 调用;否则会报 TypeError。

async def 装饰器函数本身必须是协程吗?
不是。装饰器函数本身不需要是 async def,但它的返回值(即包装后的函数)必须是协程函数,才能被 await 调用。常见错误是写了一个同步装饰器,却试图去 await 一个普通函数,结果报 TypeError: object XXX can't be used in 'await' expression。
关键判断点:如果你要装饰的目标函数是 async def,那装饰器内部的包装函数也必须用 async def 定义,并在内部 await 原函数;如果目标函数是同步的,你仍可返回一个协程函数来“异步化”它(比如加延迟、IO 模拟等),但要注意调用方必须显式 await。
- 装饰 async 函数 → 包装函数必须是
async def - 装饰 sync 函数但想返回协程 → 包装函数也得是
async def,并在内部return或await同步结果 - 用
@functools.wraps(func)保留原函数元信息,否则inspect.iscoroutinefunction()可能误判
如何正确处理带参数的 async 装饰器(如 @retry(delay=1))?
带参数的异步装饰器需要三层嵌套:外层接收装饰器参数,中层接收被装饰函数,内层是实际执行的协程。容易踩的坑是混淆闭包作用域,导致 func 在内层不可见,或参数绑定错乱。
典型结构如下:
def retry(delay=1, max_attempts=3):
def decorator(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
for i in range(max_attempts):
try:
return await func(*args, **kwargs)
except Exception:
if i == max_attempts - 1:
raise
await asyncio.sleep(delay)
return wrapper
return decorator
- 必须确保
wrapper是async def,且内部用await func(...) -
max_attempts和delay要在decorator闭包里捕获,不能放到wrapper参数里 - 若漏掉
@functools.wraps(func),被装饰函数的__name__会变成wrapper,影响日志、调试和框架路由识别
sync 函数被 async 装饰器包装后,还能直接调用吗?
不能。一旦装饰器返回的是协程函数(async def),就必须用 await 调用,否则得到的是一个 coroutine 对象,而不是实际返回值。常见错误现象是函数“没执行”或打印出 。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 错误写法:
result = my_async_decorated_func()→result是协程对象 - 正确写法:
result = await my_async_decorated_func()(在 async 上下文中)或result = asyncio.run(my_async_decorated_func())(顶层同步调用) - 若需兼容 sync/async 调用,得额外判断
inspect.iscoroutinefunction(func)并分支处理,但会显著增加复杂度,一般不建议
为什么用 asyncio.create_task() 替代直接 await 可能出问题?
在装饰器中误用 asyncio.create_task() 是个隐蔽陷阱。例如,在 wrapper 中写 task = asyncio.create_task(func(...)) 然后直接 return task,会导致调用方拿到的是未完成的 Task 对象,而非结果——除非调用方自己 await task,否则任务可能被丢弃或未调度。
更严重的是,如果装饰器逻辑依赖函数执行完毕后的副作用(如日志、状态更新),而只创建 task 不 await,那些副作用根本不会发生。
- 装饰器中应优先用
await func(...),保证串行执行和结果传递 - 仅当明确需要并发启动多个被装饰函数(如批量触发)时,才在外部统一 create_task,而不是藏在装饰器内部
-
asyncio.ensure_future()和create_task()行为类似,同样不解决“等待结果”问题
装饰器的异步性本质是“返回协程函数”,不是“内部用了 async”。最易忽略的一点:你写的 wrapper 函数体里哪怕只有一行 return await func(),也必须声明为 async def,少一个 async,整个链路就断了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










