生成器(generator)
在看生成器之前我们先来看一下列表生成式。
如果我们想得到一个12,22,32…… 102组成的列表,我们可以考虑下面的做法:
1 a=[x*x for x in range(1,11)] 2 print(a)
输出:
——————————
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
——————————
这是Python的简洁的体现之一,我们可以用这种方式快速得到一个列表。其实他还有很多玩法:
1 import pprint 2 a=[(x,y )for x in range(3) for y in "abc"] 3 pprint.pprint(a)#pprint()是让打印的结果更美观,不至于太长 4 5 b=[abs(x) for x in range(-5,5)] 6 print(b)
输出:
————————
[(0, ‘a‘),
(0, ‘b‘),
(0, ‘c‘),
(1, ‘a‘),
(1, ‘b‘),
(1, ‘c‘),
(2, ‘a‘),
(2, ‘b’),
(2, ‘c‘)]
[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]
————————
下面就让我们正式看一下生成器。得到一个生成器的方法其实很简单,我们只需要把列表生成式里的[]改成()即可,如下:
1 a=(x*x for x in range(1,11)) 2 print(a)
输出:
——————————
<generator object <genexpr> at 0x0398BAA8>
——————————
我们打印a,得到这样的结果,说明a是一个生成器,他的内存地址为:0x0398BAA8。这里显然还没有生成a中的数据,和列表生成式有很大不同。列表生成式在运行时会在内存中产生列表中的所有数据(即使我们并没有用到这些数据),而当我们没有使用到数据时,生成器则并没有生成任何一个数据。看起来生成器相当的懒惰啊。没错,我们把生成器又叫做惰性序列。但是这里的懒惰是褒义的,因为他节省了内存空间。
那怎样才能让生成器生成数据呢?答案:next
1 a=(x*x for x in range(1,11)) 2 print(a.__next__()) 3 print(a.__next__()) 4 print(a.__next__()) 5 print(a.__next__()) 6 print(a.__next__()) 7 print(a.__next__()) 8 print(a.__next__()) 9 print(a.__next__()) 10 print(a.__next__()) 11 print(a.__next__()) 12 print(a.__next__()) 13 #上面的11个a.__next()__改为11个next(a)效果不变
输出:
——————
1
4
9
16
25
36
49
56
81
100
error:StopIteration
——————
我们看到生成器中有10个数字,我们调用了11次next(或__next__),每调用一次,生成器生成下一个值并且释放上一个值,当已经生成所有的之后,就抛出StopIteration的错误。可以看到这样对计算机的内存是极大的节省。但是这样每次都调用next的方式未必太傻叉点了吧。有更好的方法吗?答案:for循环。
1 a=(x*x for x in range(1,11)) 2 for i in a: 3 print(i)
输出:
————————
1
4
9
16
25
36
49
64
81
100
————————
这样就优雅多了。
上面的那种用把列表生成式的中括号改成圆括号得到的生成器的原理是什么呢?为什么他就可以惰性的产生数据呢?这就涉及到yield了。
我们先看下面的一个函数:
1 def doublenum(n): 2 for i in range(1,n+1): 3 if i%2==0: 4 print(i) 5 doublenum(10)
输出结果:
————————
2
4
6
8
10
————————
下面我把他改一下:
1 def doublenum(n): 2 for i in range(1,n+1): 3 if i%2==0: 4 yield i #把原来的print(i)改成了yield i 5 print(doublenum(10))
输出:
————————
<generator object doublenum at 0x02AE7698>
————————
What happened!!!它变成了一个生成器!,我们看看可以用next来生成数据吗?
1 def doublenum(n): 2 for i in range(1,n+1): 3 if i%2==0: 4 yield(i) 5 g=doublenum(10) 6 print("循环的方式:") 7 for i in g: 8 print(i) 9 print("\nnext的方式:") 10 print(next(g)) 11 print(next(g)) 12 print(next(g)) 13 print(next(g)) 14 print(next(g))
输出:
————————
循环的方式:
2
4
6
8
10
next的方式:
2
4
6
8
10
StopIteration
————————
可见他确实变成了生成器了。
其实生成器就是含有yield的函数或者类(明白了吗?上面的函数含有yield,所以他编程了生成器。)
下面为了让你更明白生成器,我来写一个有意思的东西:
1 def consumer(name): 2 print("%s准备吃包子了……"%name) 3 while True: 4 baozi=yield 5 print("%s已经吃完%s了"%(name,baozi)) 6 def producer(baozi_name1,baozi_name2): 7 xiaoming=consumer("小明") 8 xiaohong=consumer("小红") 9 xiaoming.__next__() 10 xiaohong.__next__() 11 print("我做了一个%s和一个%s,分别送给小明和小红"%(baozi_name1,baozi_name2)) 12 xiaoming.send(baozi_name1) 13 xiaohong.send(baozi_name2) 14 15 for i in range(10): 16 producer("韭菜包","奶黄包")
输出:
————————
小明准备吃包子了……
小红准备吃包子了……
我做了一个韭菜包和一个奶黄包,分别送给小明和小红
小明已经吃完韭菜包了
小红已经吃完奶黄包了
小明准备吃包子了……
小红准备吃包子了……
我做了一个韭菜包和一个奶黄包,分别送给小明和小红
小明已经吃完韭菜包了
小红已经吃完奶黄包了
小明准备吃包子了……
小红准备吃包子了……
我做了一个韭菜包和一个奶黄包,分别送给小明和小红
小明已经吃完韭菜包了
小红已经吃完奶黄包了
小明准备吃包子了……
小红准备吃包子了……
我做了一个韭菜包和一个奶黄包,分别送给小明和小红
小明已经吃完韭菜包了
小红已经吃完奶黄包了
小明准备吃包子了……
小红准备吃包子了……
我做了一个韭菜包和一个奶黄包,分别送给小明和小红
小明已经吃完韭菜包了
小红已经吃完奶黄包了
小明准备吃包子了……
小红准备吃包子了……
我做了一个韭菜包和一个奶黄包,分别送给小明和小红
小明已经吃完韭菜包了
小红已经吃完奶黄包了
小明准备吃包子了……
小红准备吃包子了……
我做了一个韭菜包和一个奶黄包,分别送给小明和小红
小明已经吃完韭菜包了
小红已经吃完奶黄包了
小明准备吃包子了……
小红准备吃包子了……
我做了一个韭菜包和一个奶黄包,分别送给小明和小红
小明已经吃完韭菜包了
小红已经吃完奶黄包了
小明准备吃包子了……
小红准备吃包子了……
我做了一个韭菜包和一个奶黄包,分别送给小明和小红
小明已经吃完韭菜包了
小红已经吃完奶黄包了
小明准备吃包子了……
小红准备吃包子了……
我做了一个韭菜包和一个奶黄包,分别送给小明和小红
小明已经吃完韭菜包了
小红已经吃完奶黄包了
————————
上面是一个生产者消费者模型,生产者不停的生产包子,消费者不停的吃包子,这样程序就在两个函数之间交替执行,由于运行速度非常快,就给了用户一种并行的幻觉。这就在单线程的情形之下实现了“假并行”。这其实叫做协程。注意send函数有两个功能:
1.重新唤醒消费者,使其继续执行。
2.将参数发送给yield,然后yield将其赋值给包子变量。
总的来说:生成器就是一种惰性执行的函数,每一次的next都将使函数执行到yield,然后停止,直到下一个next的唤醒,唤醒之后有接着上一次的yield处继续执行。
迭代器(Iterator)
Python中的迭代器与c++中的迭代器有很大的不同,不可将二者进行类比。在Python中判断一个对象是否为迭代器就是看这个对象是否拥有next()(或__next__())这一方法,有即是,没有就不是。所以上面讲的生成器就是迭代器。
对于迭代器的掌握大家掌握上面这一点即可,主要掌握生成器。
总结
yield可以实现协程,主要要理解yield的用法。