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,不用建中间列表

下一篇对比生成器和列表推导式——什么时候用哪个。