一、什么是生成器
在Python中,由于受到内存的限制,列表容量肯定是有限的。例如我们创建一个包含一亿个元素的列表,Python首先会在内存中开辟足够的空间来存储这个包含一亿个元素的列表,然后才允许用户去使用这个列表,这就可能会导致以下问题:
1、内存中没有足够的内存空间开存储这个列表,从而导致列表无法创建
2、即使列表成功创建,然而仍会消耗很长的时间,导致程序效率低下
3、若用户只想访问列表前面的几个元素,则后面列表绝大多数元素占用的空间就都白白浪费了
为了有效解决以上的问题,Python中引入了一种“一边循环,一边计算”的新机制,即当用户需要使用某个对象时,Python才根据事先设计好的规则开辟内存空间创建这个对象供用户使用,而不是像列表一样事先将所有的对象都创建完毕之后再提供给用户使用。这种机制在Python中成为生成器(generator)。
二、生成器的创建
A、生成器推到式
与列表推到式类似,只不过生成器推导式使用()而非[],并且最终返回的是生成器而非列表
1 g=((i+2)**2 for i in range(2,30)) #g是一个生成器 2 print(g) #g为空,里面包含任何元素
B、yield关键字
在一个函数定义中包含yield关键字,则这个函数就不再是一个普通的函数,而是一个生成器(generator)
[说明]:yield指令可以暂停一个函数并返回其中间结果,使用该指令的函数将保存执行环境,并在必要时恢复
1 def fib(max): 2 n,a,b=0,0,1 3 while n<max: 4 #print(b) 5 yield b 6 a,b=b,a+b 7 n+=1 8 return 'done' 9 10 f=fib(6) 11 print(f)
[注]:普通函数和变成生成器的函数的不同:
普通函数是顺序执行的,遇到return或是最后一行函数语句就返回。而变成生成器的函数在每次调用__next__()方法时执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行
1 f=fib(6) 2 print(f) 3 print(f.__next__()) 4 print(f.__next__()) 5 print('暂停一下') 6 print(f.__next__()) 7 print(f.__next__())
三、生成器方法(参考:伯乐在线)
1.close()方法:手动关闭生成器函数,后面的调用会直接返回StopIteration异常
1 def func(): 2 yield 1 3 yield 2 4 yield 3 5 6 g=func() 7 g.__next__() 8 g.close() #手动关闭生成器 9 g.__next__() #关闭后,yield 2和yield 3语句将不再起作用
2.__next__()方法:返回生成器的下一次调用
1 def func(): 2 n=1 3 for i in range(3): 4 yield n 5 n+=1 6 7 c=func() 8 a1=c.__next__() 9 a2=c.__next__() 10 a3=c.__next__()
[流程解释]:
对于普通的生成器,第一个__next__()方法的调用相当于启动生成器,此时会从生成器函数的第一行开始执行,直到第一次执行完yield语句(第四行)后,跳出生成器函数。
当调用第二个__next__()方法后,会重新进入生成器函数,并从yield语句的下一条语句(第五行)开始执行,直到重新运行到yield语句,执行后再次跳出生成器函数。
后面的__next__()方法调用以此类推
3.send()方法:接受外部传入的一个变量,并根据变量内容计算结果返回到生成器函数中
[注]:
(1)send()方法和__next__()方法相似,区别在于send()方法可以传递给yield表达式值,而__next__()方法不能传递特定的值,只能传递None给yield表达式,因此可以将generator.__next__()理解为generator.send(None)
(2)第一次调用生成器函数时,必须使用__next__()语句或是send(None),不能使用send发送一个非None的值给生成器函数,否则会出错,因为没有yield语句来接收这个值
1 def gen(): 2 value=0 3 while True: 4 receive=yield value 5 if receive=='end': 6 break 7 value='Got:%s' %receive 8 9 g=gen() 10 print(g.__next__()) #或是print(g.send(None)),从而启动生成器 11 print(g.send('aaa')) 12 print(g.send(3)) 13 print(g.send('end'))
[流程解释]:
a.通过g.send(None)或g.__next__()启动生成器函数,并执行到第一个yield语句结束的位置并将函数挂起。此时执行完了yield语句,但是没有给receive赋值,因此yield value会输出value的初始值0
b.g.send('aaa')先将字符串‘aaa’传入到生成器函数中并赋值给receive,然后从yield语句的下一句重新开始执行函数(第五句),计算出value的值后返回到while头部开始新一轮的循环,执行到yield value语句时停止,此时yield value会输出‘Got:aaa’,然后挂起
c.g.send(3)重复步骤b,最后输出结果为‘Got:3'
d.g.send('end')会使程序执行break然后跳出循环,从而函数执行完毕,得到StopIteration异常
4.throw()方法:向生成器发送一个异常。
1 def gen(): 2 while True: 3 try: 4 yield 'normal value' #返回中间结果,此处的yield和return的功能相似 5 yield 'normal value2' 6 print('I am here') 7 except ValueError: 8 print('We got ValueError') 9 except Exception: 10 print('Other errors') 11 break 12 13 g=gen() 14 print(g.__next__()) 15 print(g.throw(ValueError)) 16 print(g.__next__()) 17 print(g.throw(TypeError))
[解释]:
a.print(g.__next__())会输出normal value,并停在yield 'normal value2'之前
b.由于执行了g.throw(ValueError),所以回跳过后续的try语句,即yield ‘normal value2’不会执行,然后进入到except语句,打印出‘We got ValueError’。之后再次进入到while语句部分,消耗一个yield,输出normal value
c.print(g.__next__())会执行yield ‘normal value2’语句,并停留在执行完该语句后的位置
d.g.throw(TypeError)会跳出try语句,因此print('I am here')不会被执行,然后打印‘Other errors’,并执行break语句跳出while循环,然后到达程序结尾,打印StopIteration异常的信息
四、生成器的运用
1 import time 2 def consumer(name): 3 print('%s准备吃包子啦!' %name) 4 while True: 5 baozi=yield #接收send传的值,并将值赋值给变量baozi 6 print('包子[%s]来了,被[%s]吃了!' %(baozi,name)) 7 8 9 def producer(name): 10 c1=consumer('A') #把函数变成一个生成器 11 c2=consumer('B') 12 c1.__next__()#调用这个方法会走到yield处暂时返回 13 c2.__next__() 14 print('开始准备做包子啦!') 15 for i in range(10): 16 time.sleep(1) 17 print('做了一个包子,分成两半') 18 c1.send(i) 19 c2.send(i) 20 21 producer('Tomwenxing')