跳到主要内容

生成器

1. 什么是生成器

  • 生成器(generator)一边循环一边计算的机制,不一次性创建完整 list,按需算出下一个元素,节省内存
  • 列表生成式会一次性生成整个 list;若元素可按算法推算,用生成器可在循环中边算边取,不必占用大量空间。

2. 创建方式一:列表生成式的 [] 改成 ()

  • 把列表生成式的 [] 改成 (),得到的就是 generator,不是 list。
L = [x * x for x in range(10)]   # list:[0, 1, 4, ..., 81]
g = (x * x for x in range(10)) # generator 对象
  • L 和 g 的区别:仅在于最外层的 []();L 是 list,g 是 generator。

3. 获取 generator 的元素

next(g)

  • next(g):计算出 generator 的下一个元素并返回;没有更多元素时抛出 StopIteration
  • generator 保存的是算法,每次 next(g) 才算出下一个值。
g = (x * x for x in range(10))
next(g) # 0
next(g) # 1
# ... 直到 next(g) 抛出 StopIteration

for 循环(推荐)

  • generator 是可迭代对象,用 for 循环迭代即可,不必手动 next(),也不必处理 StopIteration
g = (x * x for x in range(10))
for n in g:
print(n) # 0, 1, 4, 9, ..., 81

记忆:创建 generator 后,基本用 for 迭代,很少直接调 next()

4. 创建方式二:函数 + yield

  • 若推算逻辑复杂,列表生成式写不出来,可用函数实现;在函数里用 yield 代替 return,函数就变成 generator 函数
  • 调用 generator 函数返回一个 generator 对象,不会立刻执行完函数体;每次 next() 或 for 取下一个值时,才执行到下一个 yield 并返回。
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b # 遇到 yield 就暂停并返回值
a, b = b, a + b
n = n + 1
return 'done'

f = fib(6) # f 是 generator 对象,不是 'done'
for n in f:
print(n) # 1, 1, 2, 3, 5, 8
  • a, b = b, a + b:相当于 t = (b, a+b); a, b = t[0], t[1],不必写临时变量。

5. generator 函数的执行流程

  • 普通函数:顺序执行,遇到 return 或执行完最后一行就返回。
  • generator 函数:每次调用 next() 时执行,遇到 yield中断并返回值;再次 next() 时从上次 yield 之后继续执行,直到下一个 yield 或函数结束。
def odd():
print('step 1')
yield 1
print('step 2')
yield 3
print('step 3')
yield 5

o = odd()
next(o) # 打印 step 1,返回 1
next(o) # 打印 step 2,返回 3
next(o) # 打印 step 3,返回 5
next(o) # StopIteration

注意多次调用 generator 函数会创建多个独立的 generatornext(odd()) 每次都会新建 generator,所以每次都只执行到第一个 yield(返回 1);正确写法是 g = odd(),然后对同一个 g 多次 next(g)

6. for 循环与 return 返回值

  • for 迭代 generator 时,拿不到函数里 return 的返回值(for 在遇到 StopIteration 时结束,不会把 return 值给你)。
  • 若要拿到 return 的值,必须捕获 StopIteration,返回值在 e.value 中。
g = fib(6)
while True:
try:
x = next(g)
print('g:', x)
except StopIteration as e:
print('Generator return value:', e.value) # 'done'
break

小结

要点说明
生成器边循环边计算,不一次性建完整 list,省内存
创建方式一列表生成式 [] 改成 () → generator
创建方式二函数里用 yield → generator 函数,调用返回 generator 对象
取元素next(g)for n in g;推荐 for
执行流程遇到 yield 暂停返回,下次从 yield 后继续;return 或执行完即结束
普通函数 vs generator 函数普通函数调用直接返回结果;generator 函数调用返回 generator 对象
  • 原理:在 for 循环中不断算出下一个元素,在适当条件结束;函数型 generator 遇到 return 或执行到最后一行即结束。
  • 区分r = abs(6) 得到结果;g = fib(6) 得到 generator 对象,迭代 g 才得到值。

练习:杨辉三角——写一个 generator 函数,不断 yield 下一行的 list(如 [1], [1,1], [1,2,1], ...)。