Python的__init__必须是同步方法,不能含await;异步初始化需通过__await__或异步工厂函数实现,且需手动管理状态避免重复执行。

为什么不能直接在 __init__ 里写 await
Python 的 __init__ 必须是普通同步方法,返回 None;一旦你在里面写 await some_async_func(),会直接报 SyntaxError: 'await' outside async function。这不是限制设计,而是语言规范强制的——构造函数不能是协程。所以“异步初始化”本质是绕过 __init__ 的阻塞逻辑,把耗时操作后移。
用 __await__ 让类实例可等待,但要注意它不是构造器
__await__ 的作用是让一个对象支持 await obj,但它本身不参与实例创建。你得先有实例,才能 await 它。常见错误是以为实现 __await__ 就能“自动异步构造”,结果发现 await MyClass() 报错:因为 MyClass() 返回的是未就绪的实例,还没触发初始化逻辑。
正确做法是让工厂函数返回一个“可等待对象”,比如:
class AsyncInit:
def __init__(self):
self.data = None
<pre class='brush:python;toolbar:false;'>async def _init(self):
# 模拟异步加载
await asyncio.sleep(0.1)
self.data = "ready"
def __await__(self):
return self._init().__await__()✅ 正确用法
obj = AsyncInit() # 同步构造 await obj # 触发异步初始化
注意:__await__ 必须返回一个迭代器(通常用 coroutine.__await__()),不能直接 return await ...。
立即学习“Python免费学习笔记(深入)”;
工厂函数 + async with 是更可控的异步构造模式
比起依赖 __await__,多数真实场景更适合用异步工厂函数(返回 Coroutine)配合显式 await,再封装成上下文管理器提升可读性。这样职责清晰、调试方便、错误堆栈也干净。
- 工厂函数名建议带
create_前缀,如create_database_client(),避免和普通构造混淆 - 若需资源自动释放,直接在类里实现
__aenter__/__aexit__,而不是强行塞进__await__ - 不要在工厂里做太多逻辑判断——工厂只负责“创建并初始化”,校验、配置组装应前置
示例:
class DatabaseClient:
def __init__(self, url: str):
self.url = url
self.conn = None
<pre class='brush:python;toolbar:false;'>async def connect(self):
# 模拟连接
await asyncio.sleep(0.05)
self.conn = "connected"
async def __aenter__(self):
await self.connect()
return self
async def __aexit__(self, *args):
self.conn = None使用
async def main(): async with DatabaseClient("sqlite:///db.sqlite") as db: print(db.conn) # "connected"
容易被忽略的细节:状态管理和重复 await 风险
如果一个对象实现了 __await__ 并执行了副作用(如设置 self.data),多次 await obj 可能导致重复初始化或竞态。没有内置机制阻止二次 await —— 这得你自己加状态标记。
推荐在内部加一个 _initialized 标志,并在 __await__ 中检查:
def __await__(self):
if self._initialized:
return iter([None]) # 立即完成
return self._init().__await__()另外,异步工厂返回的对象不应被当作“单例”复用——除非你明确设计为可重入,否则并发 await 同一实例可能引发未定义行为。真正需要共享状态时,应该用依赖注入或全局异步缓存(如 async_lru),而不是靠反复 await 同一个实例。


















