专项练习 · 9 分钟阅读

Python 生成器练习指南:惰性取值

生成器不只是新语法,它改变了执行方式:产生一个值,暂停,之后再继续。当完整列表浪费内存、无法构建,或者根本不需要时,这种模型特别有用。

从带 yield 的函数开始

生成器函数看起来和普通函数一样,直到执行到 yield。此时它返回一个值,并记住局部状态。下一次请求从下一行继续。想拿到全部值时用 for 循环,想一次取一个值时用 next。

def countdown(n):
    while n > 0:
        yield n
        n -= 1

for value in countdown(3):
    print(value)

暂停与继续

生成器只能消费一次

生成器对象是迭代器。耗尽之后再请求值会抛出 StopIteration。需要再次遍历数据时,要创建新的生成器。这一点和可以反复遍历的列表不同。

def pair():
    yield 1
    yield 2

gen = pair()
print(list(gen))
print(list(gen))

第二次遍历为空

用表达式写短流水线

转换逻辑简单时,生成器表达式很紧凑。圆括号就够了,不必定义具名函数。它仍然是惰性的,只有用 list 或 sum 包起来时才真正请求值。

squares = (n * n for n in range(4))
print(sum(squares))

一行式惰性流水线

把多个阶段串起来

一个生成器可以消费另一个生成器。这样可以把读取、筛选和格式化分开,而不必构建中间列表。每个阶段保持短小;流水线变长时,给阶段起清楚的名字。

def evens(values):
    for value in values:
        if value % 2 == 0:
            yield value

print(list(evens(range(6))))

一次处理一个阶段

知道什么时候该用列表

生成器用惰性内存换取随机访问和可重复遍历。需要 len、下标、排序或多轮遍历时,就转成列表。选择数据结构要看下一步操作,而不是看哪种写法最短。

values = list(n for n in range(3))
print(len(values))
print(values[1])

需要时再物化

你的练习清单

常见问题

生成器创建时就会运行吗?

不会。调用生成器函数只创建对象;推进生成器时函数体才运行。

生成器可以是无限的吗?

可以。count 和 while True 的生成器都可能无限。用 islice、break 或其他停止条件加上边界。

生成器一定比列表快吗?

它可能更省内存,也避免构建中间数据,但速度取决于具体工作。应该测量,而不是假设。

练习生成器

12 道题,覆盖 yield、next、惰性表达式和生成器耗尽。

打开练习

继续阅读: