核心是避免用普通实例属性覆盖异步属性,应通过 @cached_property 封装加载逻辑并统一访问 items;generator 只消费已就绪数据,禁止重复 await;子类重写 _load_items 而非覆盖属性,同步/异步入口需显式分离。

核心是避免用普通实例属性覆盖异步属性,同时确保 Generator(包括 async generator)访问路径统一、缓存复用、不重复 await。
别在子类中直接赋值覆盖异步属性
父类若定义 self._data 为待 await 的协程或可等待对象(如 self._loader()),子类在 __init__ 中写 self._data = [1,2,3] 就破坏了契约:
- 迭代器助手(如
async def __aiter__)仍会尝试await self._data,抛出TypeError: object list can't be used in 'await' expression - 加类型判断(
if isinstance(..., Awaitable))又让每次迭代都多一次开销
正确做法:把底层数据入口封装成 @property 或 @cached_property,由子类重写加载逻辑,而非覆盖属性值。
用统一 getter 暴露“已解析”结果
父类中定义:
@cached_property
async def items(self):
if self._items is None:
self._items = await self._load_items()
return self._items
子类只需重写 _load_items 方法返回对应 awaitable,无需碰 self._items;所有迭代器(__aiter__、async_items())都只调用 await self.items —— 一次加载、多次复用,且自动缓存。
Generator 内部禁止重复 await 同一资源
错误写法:
async def __aiter__(self):
for item in await self._items: # 每次 __aiter__ 都 await 一次
yield item
这会导致同一实例多次调用 __aiter__ 时反复 await,失去缓存意义。应改为:
- 在
items属性中完成 await + 缓存(推荐@cached_property+ 手动锁保护) - 或在
__aiter__中先检查是否已加载,未加载再 await 并缓存到实例变量
关键:Generator 本身不负责加载,只负责消费已就绪的数据。
必要时显式区分同步/异步使用场景
如果某些子类确定数据已就绪(如测试 mock 或预加载实例),可提供同步访问入口,但不要混用:
-
self.items_sync(只读属性,返回已加载的 list/tuple) -
self.items(始终是 async property,保证接口一致)
避免让外部代码自行判断“要不要 await”,把决策权收归属性 getter 内部。











