Python 中 async for 和 async with 的原理是什么?异步迭代器和异步上下文管理器怎么理解?
简化版
async for 用来遍历异步迭代器,背后依赖 __aiter__ 和 __anext__,每次取下一个元素可以异步等待;async with 用来管理异步资源,背后依赖 __aenter__ 和 __aexit__,进入和退出资源时都可以执行异步操作。它们常用于异步流式读取、异步连接、异步事务等场景。
详细版
异步迭代:
async for row in async_cursor:
handle(row)
适合数据库游标、消息流、分页 API、WebSocket 消息等“每次取数据都可能等待 I/O”的场景。
异步上下文管理:
async with session.get(url) as response:
data = await response.text()
适合异步打开和关闭资源,例如网络连接、异步文件、连接池借还、事务提交回滚。
协议方法:
| 语法 | 协议方法 |
|---|---|
async for | __aiter__、__anext__ |
async with | __aenter__、__aexit__ |
面试回答重点:普通 for / with 处理同步对象,async for / async with 处理进入、退出、取下一个元素都可能需要 await 的异步对象。
完整版教学
一、为什么需要 async for
普通迭代器的 next() 是同步返回的。如果数据来自网络、数据库或消息队列,获取下一条数据可能要等待 I/O。
如果用普通 for,等待过程可能阻塞事件循环。async for 允许“取下一项”这个动作本身是异步的。
例如异步分页:
async for item in client.iter_items():
await process(item)
每次迭代背后都可能发一次请求,但等待时不会卡住整个事件循环。
二、异步迭代器协议
异步迭代器需要实现:
def __aiter__(self):
return self
async def __anext__(self):
...
__anext__ 返回下一项;没有下一项时抛 StopAsyncIteration。
简化示例:
class AsyncCounter:
def __init__(self, n):
self.n = n
self.i = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.i >= self.n:
raise StopAsyncIteration
await asyncio.sleep(0.1)
self.i += 1
return self.i
使用:
async for value in AsyncCounter(3):
print(value)
三、异步生成器更常用
手写 __aiter__ / __anext__ 有点繁琐。实际开发里经常用异步生成器:
async def stream_pages():
page = 1
while True:
data = await fetch_page(page)
if not data:
break
for item in data:
yield item
page += 1
它可以被 async for 消费:
async for item in stream_pages():
await process(item)
异步生成器适合表达流式数据,非常自然。
四、为什么需要 async with
普通 with 适合同步资源:
with open("a.txt") as f:
...
异步资源的进入和退出可能都需要等待。例如建立连接、发送关闭帧、释放连接回池、异步提交事务。
这时用:
async with get_connection() as conn:
await conn.query(sql)
它能保证退出时执行异步清理逻辑。
五、异步上下文管理器协议
异步上下文管理器实现:
class AsyncResource:
async def __aenter__(self):
await self.open()
return self
async def __aexit__(self, exc_type, exc, tb):
await self.close()
使用:
async with AsyncResource() as resource:
await resource.do_work()
即使代码块内抛异常,__aexit__ 也会执行,适合资源清理。
六、常见应用场景
async for 常用于:
- 异步数据库游标;
- WebSocket 消息;
- 消息队列消费;
- 流式 HTTP 响应;
- 分页接口封装。
async with 常用于:
- 异步 HTTP session;
- 数据库连接和事务;
- 异步锁;
- 连接池资源;
- 异步文件对象。
七、常见误区与追问
| 语法 | 依赖协议 | 解决的问题 |
|---|---|---|
async for | __aiter__ / __anext__ | 迭代过程中允许异步等待 |
| 异步生成器 | async def + yield | 更简洁地生产异步数据流 |
async with | __aenter__ / __aexit__ | 资源进入和释放都可 await |
同步 with | __enter__ / __exit__ | 不适合释放过程需要异步 I/O 的资源 |
- 误区:async for 只是普通 for 的语法糖。 它每次取下一个元素时可以
await,适合网络流、分页拉取、异步队列等数据源。 - 误区:异步迭代器的 anext 返回普通值就可以。
__anext__应返回 awaitable,最终产出下一个元素或抛出StopAsyncIteration。 - 误区:async with 只是在 with 前面加 async。 它要求异步上下文管理器协议,进入和退出阶段都可能执行异步 I/O。
- 追问:为什么数据库连接池常用 async with? 获取连接、开启事务、提交回滚、释放连接都可能涉及异步 I/O,
async with能把清理路径固定住。 - 追问:异步生成器和手写异步迭代器怎么选? 只是逐步产出数据时优先异步生成器;需要复杂状态封装或可复用类时再手写协议。
- 追问:async with 里发生异常会怎样? 异常会传给
__aexit__,它仍有机会执行异步清理;是否吞掉异常取决于__aexit__的返回值。
记忆钩子:
async for解决“取下一个也要等”,async with解决“进入和退出资源也要等”。
八、加强记忆
async for 解决“取下一项也要 await”的问题,背后是 __aiter__ / __anext__;async with 解决“进入和退出资源也要 await”的问题,背后是 __aenter__ / __aexit__。只要迭代、打开、关闭、提交、释放这些动作涉及异步 I/O,就该想到异步协议。