主题模式
Python 生成器与迭代器完全指南:从协议到工程实战
Python 的 for 循环为什么能遍历任何对象?列表推导式和生成器表达式有什么区别?为什么说生成器是 Python 最优雅的设计?本文从迭代器协议出发,完整解析生成器的原理、用法、实战场景,以及与 async/await 的内在联系——让你彻底掌握 Python 最精妙的特性之一。
本文由小虾子 撰写
迭代器协议:Python for 循环的真相
for 循环的工作原理
python
# Python 的 for 循环不是索引遍历,而是协议遍历
# for item in iterable 的等价代码:
iterator = iterable.__iter__() # ① 获取迭代器
while True:
try:
item = iterator.__next__() # ② 尝试取下一个元素
except StopIteration: # ③ 元素取完时抛出 StopIteration
break # 退出循环
# ④ 处理元素
process(item)
# 这就是为什么 for 可以遍历列表、字符串、文件、生成器...
# 只要对象实现了 __iter__ 和 __next__,就能用 for 遍历迭代器协议定义
python
class Counter:
"""自定义迭代器:从 1 数到 n"""
def __init__(self, n):
self.n = n
self.current = 0
def __iter__(self): # ① 返回迭代器本身
return self
def __next__(self): # ② 返回下一个元素
self.current += 1
if self.current > self.n:
raise StopIteration # 元素取完,必须抛出 StopIteration
return self.current
# 使用
for num in Counter(5):
print(num) # 1, 2, 3, 4, 5
# 等价于
counter = Counter(5)
iterator = iter(counter) # 调用 __iter__
while True:
try:
num = next(iterator) # 调用 __next__
print(num)
except StopIteration:
break迭代器 vs 可迭代对象
python
# 可迭代对象(Iterable):实现了 __iter__(返回迭代器)
# 迭代器(Iterator):实现了 __iter__ 和 __next__(本身也是可迭代的)
# 列表是可迭代对象,不是迭代器
my_list = [1, 2, 3]
print(dir(my_list))
# ['__iter__', ...] 正确 有 __iter__
# ['__next__', ...] 错误 没有 __next__ → 不是迭代器
# iter() 作用于列表,返回列表迭代器
list_iter = iter(my_list)
print(type(list_iter)) # list_iterator
# 列表迭代器是真正的迭代器
print(dir(list_iter))
# ['__iter__', '__next__', ...] 正确 两者都有
# 一个对象可以同时是迭代器和可迭代对象
# 但通常:可迭代对象 __iter__ 返回新迭代器,迭代器 __iter__ 返回 self
class Counter(Iterator): # Python 3.x 可直接继承 Iterator
"""直接继承 Iterator 更简洁"""
def __init__(self, n):
self.n = n
self.current = 0
def __iter__(self):
return self
def __next__(self):
self.current += 1
if self.current > self.n:
raise StopIteration
return self.current生成器函数:yield 的魔法
从普通函数到生成器函数
python
# 普通函数:一次性返回所有结果
def square_numbers(nums):
results = []
for n in nums:
results.append(n * n)
return results
# 生成器函数:按需产生结果(惰性求值)
def square_numbers_gen(nums):
for n in nums:
yield n * n # yield = 暂停并返回值
# 调用普通函数:立即执行,返回列表
result1 = square_numbers([1, 2, 3])
print(result1) # [1, 4, 9]
# 调用生成器函数:返回一个生成器对象(不执行函数体!)
gen = square_numbers_gen([1, 2, 3])
print(gen) # <generator object square_numbers_gen at 0x...>
print(type(gen)) # <class 'generator'>
# 逐个取值
print(next(gen)) # 1(函数执行到第一个 yield)
print(next(gen)) # 4(从上次 yield 继续,执行到第二个 yield)
print(next(gen)) # 9(从上次 yield 继续,执行到第三个 yield)
print(next(gen)) # StopIteration(函数结束)生成器的执行模型
生成器函数的执行流程:
─────────────────────────────────
def my_generator():
print("A") ← 执行(0%)
yield 1 ← 暂停(100%),返回 1
print("B") ← 恢复执行
yield 2 ← 暂停,返回 2
print("C")
yield 3 ← 暂停,返回 3
print("D")
return "完成" ← 函数结束
gen = my_generator() # 不打印任何东西!函数还没开始执行
next(gen) # 打印 "A",输出 1
next(gen) # 打印 "B",输出 2
next(gen) # 打印 "C",输出 3
next(gen) # 打印 "D",StopIteration: 完成(return 的值在异常中)
# for 循环自动处理 StopIteration
for item in my_generator():
print(item) # 1, 2, 3生成器的状态
python
import inspect
def running_gen():
yield 1
gen = running_gen()
print(inspect.getgeneratorstate(gen)) # GEN_CREATED(刚创建,未启动)
next(gen)
print(inspect.getgeneratorstate(gen)) # GEN_SUSPENDED(暂停在 yield 处)
next(gen, None) # 触发 StopIteration,gen 变为 closed
print(inspect.getgeneratorstate(gen)) # GEN_CLOSED(已关闭)
# 生成器的方法
gen = running_gen()
gen.send(42) # 向生成器发送值(yield 会接收发送的值)
gen.throw(ValueError) # 向生成器抛出异常
gen.close() # 强制关闭生成器yield from:生成器委托
yield from 的作用
python
# 问题:如何遍历嵌套结构?
def flatten(nested):
for sublist in nested:
for item in sublist:
yield item # 双重循环,繁琐
# 正确 正确方案:yield from(Python 3.3+)
def flatten(nested):
for sublist in nested:
yield from sublist # yield from = 委托给子迭代器
# 等价于
def flatten(nested):
for sublist in nested:
for item in sublist:
yield item
# 使用
data = [[1, 2], [3, 4], [5, 6]]
print(list(flatten(data))) # [1, 2, 3, 4, 5, 6]yield from 的委托机制
python
# yield from 的完整等价形式:
def generator(parent_iterable):
iterator = iter(parent_iterable)
while True:
try:
item = next(iterator)
yield item
except StopIteration:
break
# yield from 更强大之处:双向通道
def echo_gen():
"""回声生成器"""
while True:
received = yield
print(f"收到: {received}")
def delegator():
"""委托生成器"""
gen = echo_gen()
next(gen) # 启动回声生成器
yield from gen # 委托所有 send() 给回声生成器
# 测试
g = delegator()
g.send(None) # 启动委托生成器
g.send("你好") # 收到: 你好
g.send("Python") # 收到: Pythonyield from 与异常传播
python
# yield from 自动传播 StopIteration / return / 异常
def parent():
yield from child() # child 的返回值成为 parent 的返回值
def child():
for i in range(3):
yield i
return "child done"
for item in parent():
print(item) # 0, 1, 2
# parent() 的 return 值可以通过 .send() 或 catch StopIteration 获取
# 自动异常传播
def bad_child():
raise ValueError("子生成器错误")
def parent_bad():
yield from bad_child() # ValueError 会自动传播到 parent
try:
list(parent_bad())
except ValueError as e:
print(f"捕获异常: {e}") # 子生成器的异常被传播生成器表达式 vs 列表推导式
语法对比
python
# 列表推导式(立即计算,生成列表)
squares_list = [x * x for x in range(10)]
print(squares_list) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
print(type(squares_list)) # <class 'list'>
# 生成器表达式(惰性求值,生成生成器)
squares_gen = (x * x for x in range(10))
print(squares_gen) # <generator object>
print(type(squares_gen)) # <class 'generator'>
# 两者区别:内存 vs 时间
print(list(squares_gen)) # 强制转换后和列表一样性能对比
python
import sys
# 内存占用对比
list_size = sys.getsizeof([x for x in range(1000000)]) # 列表大小
gen_size = sys.getsizeof((x for x in range(1000000))) # 生成器大小
print(f"列表占用: {list_size:,} bytes") # ~8MB
print(f"生成器占用: {gen_size:,} bytes") # ~200 bytes
# 时间对比
import time
def timer(func):
start = time.perf_counter()
result = func()
elapsed = time.perf_counter() - start
return elapsed, result
# 列表:构造时就需要计算所有值
list_time, _ = timer(lambda: [x for x in range(1000000)])
# 生成器:构造时什么都不做
gen_time, _ = timer(lambda: (x for x in range(1000000)))
print(f"列表构造时间: {list_time:.4f}s") # 需要完整计算
print(f"生成器构造时间: {gen_time:.8f}s") # 几乎为 0
# 取前 10 个元素:生成器更优
gen = (x for x in range(1000000))
first_10 = [next(gen) for _ in range(10)] # 只计算前 10 个使用场景选择
选择指南:
─────────────────────────────────
用列表推导式 []:
正确 需要多次遍历
正确 需要 len() / 索引访问
正确 需要排序 / 反转
正确 数据集较小(< 10000)
正确 需要立即看到全部结果
用生成器表达式 ():
正确 只遍历一次(流式数据)
正确 数据集非常大或无限
正确 内存敏感场景
正确 只需要前 N 个元素
正确 管道处理(多个生成器串联)实战:生成器管道模式
数据处理管道
python
# 生成器天然适合数据处理管道
def read_lines(filepath):
"""读取文件行(惰性)"""
with open(filepath, encoding="utf-8") as f:
for line in f:
yield line.rstrip('\n')
def filter_non_empty(lines):
"""过滤空行"""
for line in lines:
if line.strip():
yield line
def to_upper(lines):
"""转大写"""
for line in lines:
yield line.upper()
def add_line_number(lines):
"""添加行号"""
for i, line in enumerate(lines, 1):
yield f"{i:4d}: {line}"
# 管道组合(惰性求值,内存高效)
lines = to_upper(add_line_number(filter_non_empty(read_lines("large_file.txt"))))
# 只取前 100 行:只读取并处理前 100 行
for line in lines:
print(line)
if line.startswith(" 100"):
break多步数据转换
python
# 电商数据处理管道
def parse_orders(order_strings):
"""解析订单数据"""
import json
for s in order_strings:
try:
yield json.loads(s)
except json.JSONDecodeError:
continue
def filter_valid(orders):
"""过滤有效订单"""
for order in orders:
if order.get("status") == "paid" and order.get("total", 0) > 0:
yield order
def enrich_with_customer(orders):
"""补充客户信息"""
for order in orders:
order["customer_name"] = get_customer_name(order["customer_id"])
yield order
def calculate_metrics(orders):
"""计算指标"""
total_revenue = 0
order_count = 0
for order in orders:
total_revenue += order["total"]
order_count += 1
yield {
"running_count": order_count,
"running_revenue": total_revenue,
"order": order
}
# 使用:惰性处理百万级订单
order_stream = parse_orders(large_order_strings) # 百万条 JSON
valid_orders = filter_valid(order_stream) # 只处理有效的
enriched = enrich_with_customer(valid_orders) # 补充信息
metrics = calculate_metrics(enriched) # 计算指标
# 取前 10 条指标
top_10 = [next(metrics) for _ in range(10)]itertools 标准库
无限迭代器
python
import itertools
# count():无限计数器
counter = itertools.count(start=1, step=2) # 1, 3, 5, 7, ...
print(next(counter)) # 1
print(next(counter)) # 3
# cycle():无限循环
cycler = itertools.cycle(["A", "B", "C"]) # A, B, C, A, B, C, ...
print(next(cycler)) # A
# repeat():重复一个值
repeater = itertools.repeat("x", times=3) # x, x, x
print(list(repeater)) # ['x', 'x', 'x']有限迭代器
python
import itertools
# accumulate():累积计算
data = [1, 2, 3, 4, 5]
print(list(itertools.accumulate(data))) # [1, 3, 6, 10, 15]
print(list(itertools.accumulate(data, func=max))) # [1, 2, 3, 4, 5]
# chain():连接多个可迭代对象
print(list(itertools.chain([1, 2], "abc", [3, 4]))) # [1, 2, 'a', 'b', 'c', 3, 4]
# chain.from_iterable():展平嵌套列表
nested = [[1, 2], ["a", "b"], [True]]
print(list(itertools.chain.from_iterable(nested))) # [1, 2, 'a', 'b', True]
# compress():按条件筛选
data = ["a", "b", "c", "d"]
selector = [1, 0, 1, 1]
print(list(itertools.compress(data, selector))) # ['a', 'c', 'd']
# dropwhile / takewhile
data = [1, 3, 5, 2, 4, 6]
print(list(itertools.dropwhile(lambda x: x < 5, data))) # [5, 2, 4, 6](从第一个不满足条件开始保留)
print(list(itertools.takewhile(lambda x: x < 5, data))) # [1, 3](遇到不满足条件停止)
# islice():切片(支持无限迭代器)
print(list(itertools.islice(range(100), 5, 20, 2))) # [5, 7, 9, 11, 13, 15, 17, 19]组合迭代器
python
import itertools
# product():笛卡尔积
colors = ["红", "绿", "蓝"]
sizes = ["S", "M", "L"]
print(list(itertools.product(colors, sizes)))
# [('红', 'S'), ('红', 'M'), ('红', 'L'), ('绿', 'S'), ...]
# permutations():排列(顺序有关)
print(list(itertools.permutations("ABC", 2)))
# [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
# combinations():组合(顺序无关)
print(list(itertools.combinations("ABC", 2)))
# [('A', 'B'), ('A', 'C'), ('B', 'C')]
# combinations_with_replacement():带重复的组合
print(list(itertools.combinations_with_replacement("AB", 3)))
# [('A', 'A', 'A'), ('A', 'A', 'B'), ('A', 'B', 'B'), ('B', 'B', 'B')]
# 实战:生成测试用例
def generate_test_cases():
for a, b, c in itertools.product([True, False], repeat=3):
yield {"a": a, "b": b, "c": c}
print(list(generate_test_cases())) # 8 种组合groupby:分组
python
import itertools
data = [("苹果", "水果"), ("香蕉", "水果"), ("白菜", "蔬菜"), ("萝卜", "蔬菜"), ("葡萄", "水果")]
# groupby 要求数据已排序
data.sort(key=lambda x: x[1]) # 按类别排序
for category, group in itertools.groupby(data, key=lambda x: x[1]):
items = list(group)
print(f"{category}: {items}")
# 水果: [('苹果', '水果'), ('香蕉', '水果'), ('葡萄', '水果')]
# 蔬菜: [('白菜', '蔬菜'), ('萝卜', '蔬菜')]
# 注意:group 是迭代器,只能消费一次生成器与异步的内在联系
async/await 的本质
python
import asyncio
# async def 函数返回协程对象(不是生成器)
async def async_func():
return 42
coro = async_func()
print(type(coro)) # <class 'coroutine'>
print(dir(coro))
# ['send', 'throw', 'close'] ← 协程也有 send/throw/close!
# 协程和生成器都支持 send/throw/close
# 它们都是"可以暂停和恢复的可调用对象"
# 运行协程
async def main():
result = await async_func()
print(result) # 42
asyncio.run(main())生成器模拟异步(历史背景)
python
# Python 3.4 之前:使用生成器模拟协程
# 第三方库 gevent / tornado 使用生成器实现异步
def naive_async_http_get(url):
"""用生成器模拟异步 HTTP"""
def on_response(response):
# 这里需要手动 resume 生成器
pass
# 生成器在这里代表"挂起的异步操作"
response = yield ("http_get", url, on_response)
return response
# asyncio 的历史演进:
# Python 3.4: @asyncio.coroutine + yield from
# Python 3.5: async def / await(语法支持)
# Python 3.7+: asyncio.run() 简化异步生成器(async for)
python
import asyncio
# 异步生成器:在 async def 中使用 yield
async def async_data_stream():
"""模拟异步数据流"""
for i in range(10):
await asyncio.sleep(0.1) # 模拟 I/O
yield i
# 异步迭代器
class AsyncCounter:
def __init__(self, n):
self.n = n
self.current = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.current >= self.n:
raise StopAsyncIteration
await asyncio.sleep(0.05)
self.current += 1
return self.current
# 使用
async def main():
# async for:遍历异步生成器
async for item in async_data_stream():
print(item)
# async for:遍历异步迭代器
async for num in AsyncCounter(5):
print(f"计数: {num}")
# 异步列表推导式
results = [x async for x in async_data_stream() if x % 2 == 0]
print(results) # [0, 2, 4, 6, 8]
asyncio.run(main())yield from 与 await 的关系
python
# Python 3.3 前:用 yield from 委托给子生成器
def gen_a():
yield 1
yield from gen_b() # 委托给 gen_b
yield 2
# Python 3.5+:await 可以等待协程/可等待对象
async def coro_a():
return 1
async def coro_b():
return 2
async def main():
# await 委托给协程(await 后面的对象必须可等待)
result = await coro_a()
# 多个异步任务并发
r1, r2 = await asyncio.gather(coro_a(), coro_b())
# yield from gen() vs await coroutine
# 本质相同:暂停当前任务,转去执行另一个可等待对象生成器在框架中的实际应用
FastAPI 的 StreamingResponse
python
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
app = FastAPI()
async def event_stream():
"""生成器驱动的 SSE 流"""
for i in range(10):
await asyncio.sleep(1)
yield f"data: 第 {i+1} 条消息\n\n"
@app.get("/stream")
async def stream():
"""流式响应"""
return StreamingResponse(
event_stream(),
media_type="text/event-stream"
)
# FastAPI 会自动处理生成器:
# → 每次 yield 一个值就发送一个 chunk
# → 流式传输,适合长连接 / 实时推送
# 同步生成器的 FastAPI 处理
def sync_data_generator():
for row in database_query():
yield f"data: {row}\n\n".encode()
@app.get("/sync-stream")
def sync_stream():
return StreamingResponse(
sync_data_generator(),
media_type="text/event-stream"
)Django ORM 的 QuerySet
python
# Django ORM QuerySet 底层使用生成器
# 惰性查询,避免一次性加载全部数据
# 这不会立即查询数据库
users = User.objects.filter(is_active=True)
# 遍历时才真正查询(生成器驱动)
for user in users: # SELECT * FROM users WHERE is_active=True LIMIT 1000(分批)
print(user.username)
# QuerySet.iterator():显式使用生成器(避免在内存中缓存)
def process_all_users():
for user in User.objects.all().iterator(chunk_size=1000):
# 每处理 1000 条才从数据库取下一批
process(user)
# user 对象处理后立即释放内存Pydantic 的数据验证
python
# Pydantic 底层大量使用生成器思维
from pydantic import BaseModel, Field
from typing import List
class User(BaseModel):
name: str = Field(..., min_length=1)
age: int = Field(..., ge=0, le=150)
# Pydantic 的验证过程:
# 1. 逐字段验证(yield 每一步验证结果)
# 2. 错误收集(StopIteration 收集所有错误)
# 3. 一次性报告
# 自定义字段验证器(生成器模式)
class UserModel(BaseModel):
name: str
age: int
@field_validator('name')
@classmethod
def validate_name(cls, v):
if not v.strip():
raise ValueError("名字不能为空")
return v.strip()
@field_validator('age')
@classmethod
def validate_age(cls, v):
if v < 0:
raise ValueError("年龄不能为负数")
return v常见陷阱与最佳实践
陷阱 1:生成器只能消费一次
python
# 错误 错误:多次遍历已耗尽的生成器
def numbers():
yield 1
yield 2
gen = numbers()
print(list(gen)) # [1, 2]
print(list(gen)) # [] ← 生成器已耗尽,第二次遍历为空
# 正确 正确:每次需要新生成器时重新调用函数
def numbers():
yield 1
yield 2
print(list(numbers())) # [1, 2]
print(list(numbers())) # [1, 2]
# 正确 正确:用 itertools.tee 复制生成器
def numbers():
yield 1
yield 2
g1, g2 = itertools.tee(numbers()) # tee 会缓存已消费的元素
print(list(g1)) # [1, 2]
print(list(g2)) # [1, 2]陷阱 2:生成器中的 return
python
# return 值如何获取?
def gen():
yield 1
yield 2
return "done"
g = gen()
list(g) # [1, 2]
try:
next(g) # StopIteration: done
except StopIteration as e:
print(f"返回值: {e.value}") # done
# 正确 用 send() 获取返回值(协程方式)
def gen():
yield 1
yield 2
return "done"
g = gen()
next(g) # 启动
next(g) # 推进到最后
try:
g.send("trigger") # 触发 return
except StopIteration as e:
print(e.value) # done陷阱 3:生成器与异常处理
python
# 错误 错误:在 finally 中使用 yield
def bad_generator():
try:
yield 1
yield 2
finally:
yield 99 # 错误 RuntimeError: generator raised StopIteration
# 正确 正确:finally 只做清理,不做 yield
def good_generator():
resource = acquire_resource()
try:
yield 1
yield 2
finally:
release_resource(resource) # 正确 清理代码在 finally 中
# 正确 用 contextmanager 封装资源
from contextlib import contextmanager
@contextmanager
def managed_resource():
resource = acquire_resource()
try:
yield resource
finally:
release_resource(resource)
def gen_with_context():
with managed_resource() as res:
yield res
# 退出 with 块时才清理总结
迭代器协议速查:
─────────────────────────────────
__iter__():返回迭代器本身(self)
__next__():返回下一个元素,超出时 raise StopIteration
for 循环等价于:
it = iter(iterable)
while True:
try: item = next(it)
except StopIteration: break生成器速查:
─────────────────────────────────
yield:暂停函数,返回值
yield from:委托给子迭代器
生成器表达式:(x for x in iterable)
next(gen):取下一个值
gen.send(value):发送值给生成器
generator.close():强制关闭
generator.throw(Exception):抛出异常
StopIteration:生成器结束时抛出
yield from 等价于:
for item in iterable:
yield itemitertools 速查:
─────────────────────────────────
count(start, step):无限计数器
cycle(iterable):无限循环
repeat(elem, times):重复
accumulate(iterable, func):累积
chain(*iterables):连接
chain.from_iterable:展平
compress(data, selectors):条件筛选
dropwhile/takewhile:条件过滤
islice(iterable, start, stop, step):切片
product(*iterables, repeat):笛卡尔积
permutations(iterable, r):排列
combinations(iterable, r):组合
groupby(iterable, key):分组使用场景指南:
─────────────────────────────────
正确 用生成器的场景:
→ 处理大数据集(内存友好)
→ 无限序列(count, cycle, repeat)
→ 数据管道(多个处理步骤串联)
→ 流式数据(HTTP 流、文件流、实时数据)
→ 按需计算(惰性求值)
正确 用列表的场景:
→ 需要多次遍历
→ 需要索引 / len()
→ 数据量较小
→ 需要排序 / 反转Python 的生成器是惰性计算的完美体现——它让"按需生产"成为可能,让内存使用降到最低,让流式处理变得优雅。配合 itertools 标准库和异步生成器,Python 的迭代器体系构成了现代 Python 高性能编程的基石
本文由小虾子 撰写