yield:让函数变成生成器
def countdown(n):
while n > 0:
yield n
n -= 1
g = countdown(3)
print(next(g)) # 3
print(next(g)) # 2
print(next(g)) # 1
print(next(g)) # StopIteration
带 yield 的函数被叫一次时不执行,返回生成器对象。每次 next() 才执行到下一个 yield。
上一篇的 Countdown 用 yield 重写
# 手写迭代器(上一篇):~10 行
class Countdown:
def __init__(self, n): ...
def __iter__(self): return self
def __next__(self): ...
# yield 版:3 行
def countdown(n):
while n > 0:
yield n
n -= 1
90% 自定义迭代器都该用 yield 写。
状态自动保存
yield 的魔力在于:函数"暂停"时,所有局部变量保持不变;下次 next() 时从 yield 之后继续。
def trace():
print("step 1")
yield "A"
print("step 2")
yield "B"
print("step 3")
yield "C"
g = trace()
next(g) # step 1 → 'A'
next(g) # step 2 → 'B'
next(g) # step 3 → 'C'
用 for 直接遍历
for x in countdown(5):
print(x)
# 5 4 3 2 1
实战:读大文件不爆内存
def read_lines(path):
with open(path, encoding="utf-8") as f:
for line in f:
yield line.rstrip()
# 几个 G 的文件也能处理——一次只在内存留一行
for line in read_lines("huge.log"):
if "ERROR" in line:
print(line)
实战:无限生成器
def naturals():
n = 1
while True:
yield n
n += 1
# 配合 itertools.islice 取前 10 个
from itertools import islice
list(islice(naturals(), 10)) # [1, 2, ..., 10]
yield from:把另一个生成器的值"转发"
def chain(*iterables):
for it in iterables:
yield from it
list(chain([1, 2], (3, 4), "AB"))
# [1, 2, 3, 4, 'A', 'B']
yield from x 等于 for v in x: yield v,但更地道。
send:往生成器里塞数据
def echo():
while True:
msg = yield
print(f"收到: {msg}")
g = echo()
next(g) # 启动到第一个 yield
g.send("hello") # 收到: hello
g.send("world") # 收到: world
send 不常用,但 asyncio 内部就是基于这个机制。
生成器表达式:一行的小生成器
g = (x ** 2 for x in range(10)) # 注意是圆括号
print(next(g)) # 0
print(next(g)) # 1
# 直接当 sum 的参数(最常见)
sum(x ** 2 for x in range(10)) # 285,不用建中间列表
下一篇对比生成器和列表推导式——什么时候用哪个。