什么是生成器?
一、生成式 —— 一种调函数的方式
如果忽略开水、价钱,买方便面和泡方便面有何差别?买方便面可能一次买一箱,不是吃多少卖多少,但泡方便面则不会一次泡一箱,时,肯定是吃多少泡多少。
函数也是一样。如果函数的计算结果有 10000 项数据,调函数时会得到这 10000 项数据,跟买方便面一样,哪怕用不到也得买这么多。
生成式给了我们另一种选择,即两个函数的交替执行。它像是泡方便面,调一次函数拿到一项数据,不用想办法存储所有的数据,体现了延迟演算的思想,而代码却不用有多少变化。
生成式有三种形式:元组式、yield 语句、yield表达式。
二、元组推导式 —— 延迟演算
列表式很常见,它根据一组数据计算出一组新的数据,是对赋值语句的一种改进。下列代码将得到列表 [1,3,2,5] 中各元素的平方构成的新列表。
a = [k**2 for k in [1,3,2,5]]
print(a)
运行结果
[1, 9, 4, 25]
又一个例子,它能显示计算过程,注意这里没有执行 print,但依旧有结果被打印出来,说明系统立即计算了新列表的所有元素。
a = [(i,print(i))[0] for i in [1,3,2,5]]
运行结果
1
3
2
5
元组也能根据一组数据计算出一组新的数据,但有着不同的中间过程。代码做了修改,将方括号改为圆括号,结果有所不同。
a = ((i,print(i))[0] for i in [1,3,2,5])
运行结果是空的,咦,…
这表明,元组式并没有生成它自己的元素,一个都没有,这就是延迟演算。
a = ((i,print(i))[0] for i in [1,3,2,5])
i = 0
for _ in a:
if i > 1:
break
i += 1
运行结果如下。上面的代码有一个循环,for 执行了 3 遍后结束循环,相应地从 a 中取了 3 个元素,导致 print 被调用 3 次,所以将打印三项数据。
1
3
2
这就是生成式的第一种形式,上面的例子体现了列表式的立即演算和生成式的延迟演算特性。
三、yield 语句 —— 交替执行
yield 是一个 python 关键字。python 的关键字有何用处?它们用来定义运算、表达式和语句。yield 很特别,它既定义表达式,又定义语句,而像 def 这样的关键字只是用于定义语句。
假设有两个函数 f 和 g,f 调用 g。
def g():
for i in range(3):
print(f'\tin g({i})')
def f():
g()
for i in range(3):
print(f'in f({i})')
f()
运行结果如下。当 g 在工作时,f 在等待;当 f 继续工作时,g 已经结束了,呈现出顺序结构的工作模式。
in g(0)
in g(1)
in g(2)
in f(0)
in f(1)
in f(2)
g 在函数中执行 yield,可做到暂时离开执行中的 g 回到 f,待 f 执行 next 时又回到 g 中,实现 f 与 g交替执行。
def g():
for i in range(3):
print(f'\t\t\tg(),i={i}')
yield i
def f():
a = g()
print(f'f()')
for j in range(3):
b = next(a)
print(f'f(),j={j},b={b}')
f()
运行结果如下。代码中,f 首先执行 a = g() 创建一个生成器,执行到 next(a) 语句时暂停,g 开始执行直到 yield 语句,并将 yield 后的表达式的值传递给 b = next(a) 中等号左边的 b,g 暂停,f 继续执行直到下一个 next(a) 或者结束。
f()
g(),i=0
f(),j=0,b=0
g(),i=1
f(),j=1,b=1
g(),i=2
f(),j=2,b=2
四、yield 表达式 —— 双向传递数据
表达式可以出现在等号右边。使用 yield 表达式可实现 f 与 g 双向数据传送。f 执行 b = a.send©,g 执行 y = yield x。f 执行 b = a.send© 将 c 传给 y = yield x 中的 y 后等待 b,并将控制传递给 g,g 执行 y = yield x 将 x 传递给 f 的 b 后等待 y,并将控制交还给 f。
def g(x):
for i in range(10):
y = yield x
print('\t', y)
x = y
def f():
a = g(11)
b = a.send(None)
print (b)
b = a.send(b+1)
print (b)
b = a.send(b+1)
print (b)
f()
运行结果
11
12
12
13
13
五、计算斐波那契数列 —— 生成无限的数据
生成器有什么用呢?看看下面的例子。
def fib():
a = 0
b = 1
yield a
while True:
yield b
a, b = b, a+b
a = fib()
for i in range(10):
print(next(a))
for i in range(200):
print(next(a))
运行结果如下。与标准函数相比,这时 fib 才真正有了有数列的感觉。你一直调用它,它会源源不断地返回更多的后继,像是射击,开一枪,响一声,再开一枪,再响一声。
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
请设想,用类能够实现类似的功能吗?生成器是不是最简的选择?