【什么是生成器】

一、生成式 —— 一种调函数的方式

  如果忽略开水、价钱,买方便面和泡方便面有何差别?买方便面可能一次买一箱,不是吃多少卖多少,但泡方便面则不会一次泡一箱,时,肯定是吃多少泡多少。

  函数也是一样。如果函数的计算结果有 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

  请设想,用类能够实现类似的功能吗?生成器是不是最简的选择?

【下一个坑:str 与 repr 有何差别?】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值