Python生成器是用函数书写的迭代器:函数体里出现 yield 后,调用该函数得到的是生成器对象。每次 next 会运行到下一个 yield,交出值并暂停。现场保留着,下次从暂停处继续。它适合按需产出大量或无限序列,而不必一次性装进列表。
带 yield 的函数
调用 my_gen() 时函数体不会立刻跑完,只是返回生成器。真正执行发生在 next 时:
def my_gen():
yield 1
yield 2
yield 3
g = my_gen()
print(next(g))
print(next(g))
print(next(g))
# print(next(g)) # StopIteration
暂停与继续
yield 是暂停键:返回一个值并停住现场,下次 next() 从停住的地方继续跑,直到没有 yield 可停就抛 StopIteration。
def gen():
print("开始")
yield "A"
print("继续")
yield "B"
g = gen()
print(next(g))
print(next(g))
第一次 next 打印开始并返回 A。第二次从 yield "A" 之后接着跑,打印继续并返回 B。
无限序列
列表装不下永远递增的偶数序列,生成器可以边算边给。调用方用几个取几个:
def evens():
n = 0
while True:
yield n
n += 2
e = evens()
print(next(e))
print(next(e))
print(next(e))省内存:列表推导与生成器表达式
中括号会立刻建出整张列表。小括号写成生成器表达式,则同样是惰性的。注意:一个生成器求和用尽后就不能再遍历:
squares_list = [x * x for x in range(10000000)]
squares_gen = (x * x for x in range(10000000))
print(type(squares_list))
print(type(squares_gen))
print(sum(squares_gen))用 for 遍历生成器
def count_down(n):
while n > 0:
yield n
n -= 1
for x in count_down(3):
print(x, end=" ")
print()按行读大文件
在生成器里 for line in f,可以把文件内容一条条交给外层循环,避免 read() 整文件进内存:
def read_lines(path):
with open(path, "r", encoding="utf-8") as f:
for line in f:
yield line.strip()
for line in read_lines("demo.txt"):
print(line)和手写迭代器类对比
| 维度 | 生成器 | 自定义迭代器类 |
|---|---|---|
| 写法 | 函数 + yield | 类 + __iter__ / __next__ |
| 代码量 | 通常更短 | 样板更多 |
| 适用 | 状态推进直观 | 需要完整类接口时 |
常见误区
- 生成器同样只能消费一次,用尽后需重新调用生成器函数。
- 生成器函数里的
return主要用来结束迭代,返回值不会变成yield出去的元素(高级用法另论)。 - 函数里若没有任何
yield,它就只是普通函数,调用结果也不是生成器。