一、生成器的几个特点
1、含有yield关键字的函数都是生成器函数。
2、yield和return不能共存。
3、return一般一个函数中只有一个,它会将函数终止,并返回一个值给调用函数的地方。
4、yield在一个函数中可以有多个,它不会终止函数,下次还可以接着从离开它的地方继续执行。使用next()或者__next__()可以获取到yield的生成的元素。
5、生成器函数被调用后,会返回一个生成器。调用时,并不会执行生成器函数内部的代码,如下图,在调用next(g)或者g.__next__()方法的时候才会执行函数内部的代码,遇到yield关键字就不再继续往下执行了。
6、使用生成器的好处就是,可以避免一下子在内存中产生过多的数据,只是在我们需要的时候生成一部分数据就可以了。
7、yield和next是一一对应的,如果next超过了yield的数量,程序就会报错。
1 defproduce():2 """生产衣服"""
3 for i in range(2000000):4 yield "生产了第%s件衣服"%i5
6 product_g =produce()7 print(product_g.__next__()) #要一件衣服
8 print(product_g.__next__()) #再要一件衣服
9 print(product_g.__next__()) #再要一件衣服
10 num =011 for i in product_g: #要一批衣服,比如5件
12 print(i)13 num +=1
14 if num == 5:15 break
16
17 #运行结果:
18 生产了第0件衣服19 生产了第1件衣服20 生产了第2件衣服21 生产了第3件衣服22 生产了第4件衣服23 生产了第5件衣服24 生产了第6件衣服25 生产了第7件衣服
二、举例:
公司要做市场推广,需要印刷2,000,000本宣传册,单价15元。可以一次性拿出3000万让印刷厂全部印出来,搬回来找个地方放着,每次推广活动的时候拿一点发出去。
1 defbook():2 l =[]3 for i in range(1,2000000):4 l.append('第%s本宣传册'%i)5 returnl6
7 book =book()8 print(book)
这样显然不合理,从经济上讲,不可能一次性拿出3000万去印刷宣传册。从程序设计上讲,一次性生成一个2000000个元素的列表,会占用很大的内存,运行效率低。
如果能跟印刷厂签个合同,比如说2年内购买2000000册,2年内有推广活动时就让印刷厂印出一少部分,这样就完美了。
1 defbook():2 for i in range(1,2000000):3 yield '第%s本宣传册'%i4
5 g =book()6 print(next(g))7 print(next(g))8 print(g.__next__())9 print(g.__next__())10 print(next(g))11
12 for i in range(50):13 print(next(g))14
15 for i in range(100):16 print(g.__next__())
三、send
1、send和next的效果一样,都能获取到yield返回的值。
2、send在获取下一个yield的返回值的同时,给上一个yield的位置传递一个数据。
3、第一次使用生成器的时候,必须使用next回去yield的返回值。
4、最后一个yield不能接收外部传递的值。
defgenerator():print(123)
ret1= yield 'aaa'
print(ret1)print(456)
ret2= yield 'bbb'
print(ret2)
g=generator()print(g.__next__())print(g.send('我是send传过来的值')) #send将值传给了第一个yield#print(g.__next__()) # 这个地方会报错StopIteration,因为前面有一个__next__和一个send了,generator生成器里面只有两个yield
四、举例
1、获取移动平均值
defaverage():
sum=0
count=0
avg=0whileTrue:
num= yieldavg
sum+=num
count+= 1avg= sum /count
g=average()
g.__next__()
ret= g.send(10)print(ret)
ret= g.send(20)print(ret)
执行过程:
优化后的生成器,使用装饰器与激活生成器,在调用的时候直接使用send,不用在send之前调用__next__激活生成器了。
definit(func):def inner(*args,**kwargs):
g= func(*args,**kwargs)
g.__next__()returngreturninner
@initdefaverage():
sum=0
count=0
avg=0whileTrue:
num= yieldavg
sum+=num
count+= 1avg= sum /count
g=average()
ret= g.send(10)print(ret)
ret= g.send(20)print(ret)
执行过程:
五、yield from
defgen():
s= 'abcd'
for i ins:yieldifor j in range(4):yieldj
g=gen()for i ing:print(i)
defgen():
s= 'abcd'
yield fromsyield from range(4)
g=gen()for i ing:print(i)
六、列表推导式
#列表循环
l =[]for i in range(5):
l.append('鸡蛋%s'%i)print(l) #['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4']
#列表推导式
print(['鸡蛋%s'%i for i in range(5)]) #['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4']
#列表循环
s =0for i in range(5):
s+= i**2
print(s)#列表推导式
print(sum([i**2 for i in range(5)]))
七、生成器表达式
和列表推导式相比,只要把列表推导式中的[]换成()就变成一个生成器表达式。
print(x for x in range(5)) # at 0x009943B0>
print(sum(x for x in range(5))) #10#注意:这两个东西的返回值,第一个输出的是生成器,第二个输出的是sum的结果
八、各种推导式
1、列表推导式
print([i for i in range(31) if i%3 ==0])print([i*i for i in range(31) if i%3 == 0])
2、字典推导式
#key和value互换
dic = {'a':10, 'b':20}print({dic[item]:item for item in dic}) #{10: 'a', 20: 'b'}
#将key相同的value值合并(忽略key的大小写),并将key都改成小写
dic = {'a':10, 'b':20, 'A':30, 'B':40}
ret= {k.lower():dic.get(k.lower(),0) + dic.get(k.upper(),0) for k indic}print(ret) #{'a': 40, 'b': 60}
3、集合推导式
print({i**2 for i in [1,-1,2,-2,4]}) #{16, 1, 4}
九、面试题
面试题一:
defdemo():for i in range(4):yieldi
g=demo()
g1=(i for i in g) #这一句在list(g1)在执行时才会被调用
g2=(i for i in g1) #这一句在list(g2)在执行时才会被调用
print(list(g1)) #[0, 1, 2, 3]
print(list(g2)) #[]
结果解析:
为什么是这个运行结果?
生成器有一个特点,那就是如果生成器里面的值被取完了,再取就取不到了。
在上面的题目中,g1=(i for i in g)和g2=(i for i ing1)在开始的时候并没有执行,只有在执行到list(g1)和list(g2)的时候才会执行。因为生成器表达式和生成器函数一样只返回一个生成器,并不会执行里面的代码。
在执行list(g1)的时候,会强制将生成器g1的值转换成list,g1又会从生成器g中取值。所以list(g1)执行后,生成器g1里面的值已经被全部取出来了。
在执行list(g2)的时候,会强制将生成器g2的得转换成list,g2这时会从g1去取值,因为g1里面的值已经在前面被取完了,所以list(g2)就取不到任何值了。
如果将print(list(g1))这一段代码注释掉,print(list(g2))就会能取到值,返回[0, 1, 2, 3]
面试题二:
defadd(n,i):return n+ideftest():for i in range(4):yieldi
g=test()for n in [1,10]:
g=(add(n,i) for i ing)
ret=list(g)print(ret) #[20, 21, 22, 23]
#上面的程序似乎看不明白,可以把上面的for循环拆开来看就比较容易了,如下:
defadd(n,i):return n+ideftest():for i in range(4):yieldi
g=test()
n= 1g=(add(n,i) for i ing)
n= 10g=(add(n,i) for i ing)#到这里为止,上面的代码都不会执行,在list(g)被执行的时候才会去从生成器g中取值
ret =list(g)print(ret) #[20, 21, 22, 23]
思考下面的题目,为什么输出的是:[15, 16, 17, 18]
提示,将for循环也拆开来看
defadd(n,i):return n+ideftest():for i in range(4):yieldi
g=test()for n in [1,10,5]:
g=(add(n,i) for i ing)
ret=list(g)print(ret) #[15, 16, 17, 18]