生成器 / 迭代器的区别
一句话速记
迭代器(Iterator)= 实现了 __iter__ + __next__ 的对象,每次调用 next() 返回下一个值,用完抛 StopIteration。生成器(Generator)= 用 yield 定义的函数,是迭代器的一种特殊实现——调用时不执行函数体,而是返回一个 Generator 对象(已是迭代器)。生成器的核心优势:惰性求值(不一次性计算所有值)+ 保存执行状态(yield 暂停,下次 next() 继续)。LLM 流式输出就是典型的生成器模式。
通俗解释(5 分钟版)
迭代器 vs 可迭代对象:
# 可迭代对象(Iterable):有 __iter__ 方法,能 for 循环
my_list = [1, 2, 3] # list 是可迭代对象
for x in my_list: ... # 内部调用 iter(my_list) 得到迭代器
# 迭代器(Iterator):有 __iter__ 和 __next__,能 next()
my_iter = iter(my_list) # list_iterator 对象
next(my_iter) # 1
next(my_iter) # 2
next(my_iter) # 3
next(my_iter) # StopIteration!
# 关键区别:
# - 可迭代对象可以多次 for 循环(每次创建新迭代器)
# - 迭代器只能遍历一次(状态不重置)生成器 = 语法糖版迭代器:
# 手写迭代器(繁琐)
class CountUp:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration
val = self.current
self.current += 1
return val
# 生成器版本(简洁)
def count_up(start, end):
current = start
while current < end:
yield current # 暂停,返回 current;下次 next() 从这里继续
current += 1
# 使用完全一样:
for x in count_up(0, 5):
print(x) # 0 1 2 3 4关键细节
1)yield 的执行流程
def gen():
print("A")
yield 1 # 第一次 next() 返回 1,暂停在这里
print("B")
yield 2 # 第二次 next() 返回 2,暂停在这里
print("C")
# 函数结束 → 自动 raise StopIteration
g = gen() # 创建生成器对象,不执行任何代码!
print(next(g)) # 打印 "A",返回 1
print(next(g)) # 打印 "B",返回 2
print(next(g)) # 打印 "C",StopIteration关键:调用 gen() 时不执行函数体,只返回生成器对象。每次 next() 才从上次 yield 的地方继续执行到下一个 yield。
2)生成器表达式 vs 列表推导式
# 列表推导式:立即计算,全部存内存
squares_list = [x**2 for x in range(1000000)] # 占用几十MB内存
# 生成器表达式:惰性计算,按需生成
squares_gen = (x**2 for x in range(1000000)) # 几乎不占内存
# 使用相同:
for s in squares_gen:
process(s)
# 关键场景:处理大文件(不能一次性加载到内存)
def read_large_file(path):
with open(path) as f:
for line in f:
yield line.strip() # 每次只读一行到内存3)yield from(委托生成器)
# 普通写法(繁琐)
def chain(iter1, iter2):
for x in iter1:
yield x
for x in iter2:
yield x
# yield from(简洁)
def chain(iter1, iter2):
yield from iter1
yield from iter2
# yield from 还处理了 send() 和 throw() 的转发
# 在协程(asyncio)里,await 就是基于 yield from 实现的4)send() —— 双向生成器
def accumulator():
total = 0
while True:
value = yield total # yield 返回 total,同时接收 send() 发来的 value
total += value
g = accumulator()
next(g) # 启动生成器(必须先 next 到第一个 yield)
g.send(10) # → total=10,yield 返回 10
g.send(20) # → total=30,yield 返回 30
g.send(5) # → total=35,yield 返回 35send() 是 asyncio 协程的底层机制:await 在底层就是 yield from,事件循环通过 send() 把 I/O 结果传回协程。
5)LLM 流式输出的生成器模式
# OpenAI SDK 的流式响应就是生成器
from openai import OpenAI
client = OpenAI()
def stream_completion(prompt: str):
"""生成器函数,逐 token 产出"""
stream = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
yield chunk.choices[0].delta.content # 逐 token yield
# FastAPI 流式响应
from fastapi.responses import StreamingResponse
@app.post("/chat/stream")
async def chat_stream(request: ChatRequest):
async def generator():
for token in stream_completion(request.prompt):
yield f"data: {token}\n\n" # SSE 格式
return StreamingResponse(generator(), media_type="text/event-stream")6)迭代器协议 vs 异步迭代器
# 同步迭代器(__iter__ + __next__)
class SyncIter:
def __iter__(self): return self
def __next__(self): ...
# 异步迭代器(__aiter__ + __anext__)
class AsyncIter:
def __aiter__(self): return self
async def __anext__(self):
data = await fetch_data()
if not data: raise StopAsyncIteration
return data
# 异步生成器(async def + yield)
async def async_stream():
async for item in some_async_source():
yield item # 异步生成器
# 使用:
async for item in async_stream():
process(item)延伸追问
- Q:for 循环和迭代器的关系是什么?
→
for x in obj等价于:先调用iter(obj)得到迭代器,然后反复调用next(iterator)直到StopIteration。如果obj本身是迭代器,iter(obj)返回自身(因为迭代器的__iter__返回self)。 - Q:列表可以 for 多次,迭代器只能一次,为什么?
→ 列表每次
iter(list)创建新的迭代器(内部有 index,从 0 开始);列表本身不记录遍历位置,所以可以重复遍历。迭代器记录了当前状态(__next__推进),遍历完就”耗尽”,iter(exhausted_iter)返回自身(同一个耗尽的对象)。 - Q:
range(10)是可迭代对象还是迭代器? → 是可迭代对象,不是迭代器。range(10)是惰性序列(不存储所有数),每次iter(range(10))返回新的range_iterator,可以多次 for 循环。这和生成器不同——生成器只能遍历一次。
我的记法
- 可迭代 = 有
__iter__(能 for 循环,可多次) - 迭代器 = 有
__iter__+__next__(有状态,只能一次) - 生成器 = yield 函数 = 自动实现迭代器
- yield 是暂停点:函数状态保存,下次
next()从这里继续 - 生成器优势:惰性求值(大数据/流式),不一次性加载到内存
- LLM 流式输出 = 生成器模式(逐 token yield)
- 一句话:「生成器是懒人版迭代器——yield 挂起状态,next() 唤醒继续」
状态
- 已背速记
- 能讲通俗版
- 能写 LLM 流式输出的生成器代码