Python中的异步生成器(Async Generator)与async for循环
一、知识点描述
异步生成器是Python 3.6引入的特性,它结合了生成器和异步编程的特点。普通的生成器使用yield产生值,而异步生成器使用async def定义,并在其中使用yield(或yield from)产生值。异步生成器主要用于在异步协程中逐步产生一系列值,通常用于处理流式数据(如网络数据流、大文件读取等)。与async for循环配合使用,可以方便地在异步代码中迭代异步生成器产生的值。
二、循序渐进讲解
步骤1:回顾普通生成器与异步函数
普通生成器使用def定义,内部包含yield语句,调用时返回一个生成器对象,通过next()或for循环逐步取值:
def simple_gen():
yield 1
yield 2
for val in simple_gen():
print(val) # 输出 1, 2
异步函数使用async def定义,内部可以包含await表达式,用于挂起等待异步操作:
async def async_func():
await asyncio.sleep(1)
return "done"
步骤2:异步生成器的基本定义
异步生成器是async def定义的函数,内部包含yield语句。它返回一个异步生成器对象,这个对象既是一个异步可迭代对象(async iterable),也是一个异步迭代器(async iterator):
async def async_gen():
yield 1
yield 2
yield 3
# 调用异步生成器函数不会立即执行,而是返回一个异步生成器对象
gen_obj = async_gen()
print(type(gen_obj)) # <class 'async_generator'>
步骤3:async for循环的使用
async for是用于在异步上下文中迭代异步可迭代对象的语法。它只能在async def定义的函数内使用。每次迭代都会自动调用异步迭代器的__anext__()方法,并await其返回的awaitable对象:
async def main():
async for value in async_gen():
print(value) # 依次输出 1, 2, 3
# 运行异步主函数
asyncio.run(main())
步骤4:异步生成器内部使用await
异步生成器的关键优势是可以在yield之间使用await挂起,执行其他异步操作(如网络请求、文件IO等):
async def fetch_pages(urls):
for url in urls:
# 模拟异步网络请求
await asyncio.sleep(0.5) # 异步等待
yield f"Page content from {url}"
async def main():
urls = ["url1", "url2", "url3"]
async for content in fetch_pages(urls):
print(content) # 每隔0.5秒输出一个页面内容
步骤5:异步生成器的工作原理
异步生成器对象实现了两个核心方法:
__aiter__():返回自身(异步迭代器),使得async for能识别。__anext__():返回一个awaitable对象(通常是协程),每次迭代时await这个对象获取下一个值。
手动使用这些方法迭代:
async def manual_iteration():
gen = async_gen()
while True:
try:
# __anext__()返回一个可等待对象,需要await
value = await gen.__anext__()
print(value)
except StopAsyncIteration: # 异步迭代结束异常
break
步骤6:async for的底层执行流程
当执行async for item in async_gen():时:
- 调用
async_gen()返回异步生成器对象。 - 隐式调用对象的
__aiter__()方法(通常返回自身)。 - 在每次循环迭代时,隐式调用
__anext__()方法,返回一个awaitable对象。 await这个对象,得到下一个值赋给item。- 当没有更多值时,
__anext__()抛出StopAsyncIteration异常,循环结束。
步骤7:异步生成器与普通生成器的对比
- 普通生成器:使用
yield产生值,通过同步for循环或next()迭代。不能在yield之间进行异步等待。 - 异步生成器:使用
async def定义,在yield之间可以使用await。必须通过async for循环或await anext()迭代。
步骤8:异步生成器的实际应用场景
- 流式数据处理:如从异步数据库查询中逐行获取结果。
- 实时数据流:如WebSocket消息的持续接收。
- 大文件异步读取:分块读取文件而不阻塞事件循环。
- 分页API调用:异步获取多页数据并逐个产出。
示例:异步读取大文件
async def read_large_file(file_path):
with open(file_path, 'r') as file:
while True:
line = await file.readline() # 假设有异步readline
if not line:
break
yield line.strip()
async def process_file():
async for line in read_large_file("data.txt"):
# 异步处理每一行
await asyncio.sleep(0.1)
print(line)
步骤9:异步生成器的资源清理
异步生成器支持异步上下文管理器,可以使用async with确保资源正确释放:
async def async_gen_with_resource():
resource = acquire_resource() # 假设获取资源
try:
for i in range(3):
yield i
await asyncio.sleep(0.1)
finally:
resource.release() # 确保资源释放
# 或者使用异步上下文管理器
async def async_gen_with_context():
async with aiofiles.open('file.txt') as f:
async for line in f:
yield line
步骤10:anext()内置函数
Python 3.10引入了anext()内置函数,用于手动获取异步迭代器的下一个值:
async def demo():
gen = async_gen()
first = await anext(gen) # 获取第一个值
second = await anext(gen) # 获取第二个值
三、总结
异步生成器是Python异步编程的重要扩展,允许在生成值的过程中进行异步等待。它通过async def定义,使用yield产生值,必须通过async for或await anext()迭代。主要应用于需要逐步产生数据且涉及异步操作的场景(如网络流、文件IO等),能有效利用异步并发优势,避免阻塞事件循环。