14-Python-生成器

1、生成器概念

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator

 1 >>> b = (i**2 for i in range(10))
 2 >>> b
 3 <generator object <genexpr> at 0x0000022E4EFD8780>
 4 >>> for n in b:  #用for循环打印所有元素
 5     print(n)    
 6 0
 7 1
 8 4
 9 9
10 16
11 25
12 36
13 49
14 64
15 81

如果要一个一个打印出来,可以通过__next__()函数获得generator的下一个返回值

 1 >>> n = (i**2 for i in range(10))
 2 >>> print(n.__next__())
 3 0
 4 >>> print(n.__next__())
 5 1
 6 >>> print(n.__next__())
 7 4
 8 >>> print(n.__next__())
 9 9
10 >>> print(n.__next__())
11 16
12 >>> print(n.__next__())
13 25
14 >>> print(n.__next__())
15 36
16 >>> print(n.__next__())
17 49
18 >>> print(n.__next__())
19 64
20 >>> print(n.__next__())
21 81
22 >>> print(n.__next__())  #当没有更多元素时,会抛出异常
23 Traceback (most recent call last):
24   File "<pyshell#34>", line 1, in <module>
25     print(n.__next__())
26 StopIteration

2、生成器的其它姿势

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

 1 def fib(num):
 2     n, a, b = 0, 0, 1
 3     while n < num:
 4         print(b)  
 5         a, b = b, a+b
 6         n += 1
 7     return "done"  # 异常的时候打印该消息
 8 '''
 9 注意赋值语句 a, b = b, a+b,相当于如下
10 tmp = (b, a+b)  #tmp为一个元组
11 a = tmp[0]
12 b = tmp[1]
13 '''

然后执行fib()便可以打印出前N个数。仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

1 def fib(num):
2     n, a, b = 0, 0, 1
3     while n < num:
4         yield b  # 将函数变为生成器
5         a, b = b, a+b
6         n += 1
7     return "done"  # 异常的时候打印该消息

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator

 1 >>> num = fib(10)
 2 >>> num
 3 <generator object fib at 0x0000022E4EFD88E0>
 4 >>> print(num.__next__())
 5 1
 6 >>> print(num.__next__())
 7 1
 8 >>> print(num.__next__())
 9 2
10 >>> print(num.__next__())
11 3
12 >>> print(num.__next__())
13 5
14 >>> print(num.__next__())
15 8
16 >>> print(num.__next__())
17 13
18 >>> print(num.__next__())
19 21
20 >>> print(num.__next__())
21 34
22 >>> print(num.__next__())
23 55
24 >>> print(num.__next__())
25 Traceback (most recent call last):
26   File "<pyshell#65>", line 1, in <module>
27     print(num.__next__())
28 StopIteration: done

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

f = fib(10)
print(f)
print(f.__next__())
print(f.__next__())
print(f.__next__())
print("从这里开始,每一个数都由前面紧邻的两个数相加得到。")
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())

#以下为输出结果
1
1
2
从这里开始,每一个数都由前面紧邻的两个数相加得到。
3
5
8
13
21
34
55

在上面fib的例子,我们在循环过程中不断调用yield,就会不断中断(视频11节2分20秒)。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

 1 >>> num = fib(10)
 2 >>> for n in num:
 3     print(n)    
 4 1
 5 1
 6 2
 7 3
 8 5
 9 8
10 13
11 21
12 34
13 55

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue中:

 1 >>> g = fib(6)
 2 >>> while True:
 3     try:
 4         x = next(g)
 5         print("g:", x)
 6     except StopIteration as e:  # 该错误表示无法继续返回下一个值了
 7         print("Generator return value:", e.value)
 8         break
 9 #以下为输出结果
10 g: 1
11 g: 1
12 g: 2
13 g: 3
14 g: 5
15 g: 8
16 Generator return value: done

还可以通过yield实现单线程下的并行计算效果:

 1 import time
 2 
 3 
 4 def customer(name):
 5     print("%s准备吃包子啦!" % name)
 6     while True:
 7         baozi = yield  # yield的作用保存当前状态并返回
 8         print("包子[%s]来了!被[%s]吃了!" % (baozi, name))
 9 
10 
11 def producer(name):
12     c1 = customer("druid")
13     c2 = customer("alex")
14     c1.__next__()  # next只
15     c2.__next__()
16     print("老子准备开始吃包子了!")
17     for i in range(10):  # 做十个包子
18         time.sleep(1)
19         print("做了一个包子,一人一半。")
20         c1.send(i)  # 继续回到生成器,并且将值传递给yield
21         c2.send(i)
22 
23 
24 producer("druid")
25 
26 #以下为输出结果
27 druid准备吃包子啦!
28 alex准备吃包子啦!
29 老子准备开始吃包子了!
30 做了一个包子,一人一半。
31 包子[0]来了!被[druid]吃了!
32 包子[0]来了!被[alex]吃了!
33 做了一个包子,一人一半。
34 包子[1]来了!被[druid]吃了!
35 包子[1]来了!被[alex]吃了!
36 做了一个包子,一人一半。
37 包子[2]来了!被[druid]吃了!
38 包子[2]来了!被[alex]吃了!
39 做了一个包子,一人一半。
40 包子[3]来了!被[druid]吃了!
41 包子[3]来了!被[alex]吃了!
42 做了一个包子,一人一半。
43 包子[4]来了!被[druid]吃了!
44 包子[4]来了!被[alex]吃了!
45 做了一个包子,一人一半。
46 包子[5]来了!被[druid]吃了!
47 包子[5]来了!被[alex]吃了!
48 做了一个包子,一人一半。
49 包子[6]来了!被[druid]吃了!
50 包子[6]来了!被[alex]吃了!
51 做了一个包子,一人一半。
52 包子[7]来了!被[druid]吃了!
53 包子[7]来了!被[alex]吃了!
54 做了一个包子,一人一半。
55 包子[8]来了!被[druid]吃了!
56 包子[8]来了!被[alex]吃了!
57 做了一个包子,一人一半。
58 包子[9]来了!被[druid]吃了!
59 包子[9]来了!被[alex]吃了!

 

 

转载于:https://www.cnblogs.com/Druidchen/p/7911246.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值