Python 生成器:yield 关键字与迭代器
Python 中的 yield 关键字用于定义生成器函数,它能让你像写普通函数一样创建一个惰性求值的迭代器。这种机制在处理大量数据或无限序列时特别有用,因为它不会一次性将所有数据加载到内存中。
理解生成器的基本原理
-
编写一个包含
yield的函数:def count_up_to(max): count = 1 while count <= max: yield count count += 1 -
调用该函数时,不会立即执行函数体,而是返回一个生成器对象:
gen = count_up_to(3) print(type(gen)) # <class 'generator'> -
使用
next()函数从生成器中逐个获取值:print(next(gen)) # 输出: 1 print(next(gen)) # 输出: 2 print(next(gen)) # 输出: 3 # 再次调用会抛出 StopIteration 异常 -
在 for 循环中直接使用生成器,循环会自动处理
StopIteration:for num in count_up_to(5): print(num) # 输出: 1 2 3 4 5
生成器 vs 普通列表:内存效率对比
考虑一个需要生成一百万个整数的场景:
-
普通列表会一次性分配全部内存:
def make_list(n): return [i for i in range(n)] -
生成器只在需要时计算下一个值:
def make_generator(n): for i in range(n): yield i
你可以通过以下方式验证内存差异(需安装 memory_profiler):
pip install memory-profiler
然后运行:
from memory_profiler import profile
@profile
def test_list():
big_list = [i for i in range(1000000)]
return sum(big_list)
@profile
def test_generator():
big_gen = (i for i in range(1000000))
return sum(big_gen)
结果会显示生成器版本占用内存显著更少。
使用 send() 方法向生成器传递数据
生成器不仅能产出数据,还能接收外部输入:
-
定义一个能接收值的生成器:
def echo(): while True: received = yield print(f"收到: {received}") -
启动生成器并发送数据:
e = echo() next(e) # 必须先执行一次 next() 到第一个 yield e.send("你好") e.send("世界") -
结合产出与接收实现双向通信:
def accumulator(): total = 0 while True: value = yield total if value is not None: total += value -
使用这个累加器:
acc = accumulator() next(acc) # 启动到第一个 yield print(acc.send(10)) # 输出: 10 print(acc.send(20)) # 输出: 30
生成器表达式:简洁的语法糖
除了函数形式,Python 还支持类似列表推导式的生成器表达式:
-
将列表推导式的方括号
[...]改为圆括号(...):squares = (x**2 for x in range(10)) -
验证其类型和行为:
print(type(squares)) # <class 'generator'> print(list(squares)) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] -
注意:生成器只能遍历一次,再次转换为列表会得到空结果:
print(list(squares)) # []
常见应用场景
| 场景 | 实现方式 | 优势 |
|---|---|---|
| 大文件逐行处理 | def read_lines(filename):<br> with open(filename) as f:<br> for line in f:<br> yield line.strip() |
避免将整个文件载入内存 |
| 无限序列生成 | def fibonacci():<br> a, b = 0, 1<br> while True:<br> yield a<br> a, b = b, a + b |
可按需获取任意长度序列 |
| 数据管道处理 | data = (process(x) for x in source if filter(x)) |
链式操作,内存高效 |
注意事项与陷阱
-
不要混淆生成器表达式与元组:
# 这是生成器 gen = (x for x in range(3)) # 这是元组(需要逗号) tup = (1,) -
避免在生成器中使用可变默认参数,因为生成器状态会保留:
# 危险示例 def bad_example(lst=[]): lst.append(1) yield lst -
显式关闭不再需要的生成器以释放资源:
gen = some_generator() try: for item in gen: if some_condition: break finally: gen.close() # 触发 GeneratorExit 异常 -
使用
itertools模块扩展生成器功能:import itertools # 重复生成器多次 repeated = itertools.chain.from_iterable( itertools.repeat(count_up_to(3), 2) )

暂无评论,快来抢沙发吧!