为什么需要生成器:
假如现在有一个需求,要打印从1-1亿的整形。如果采用普通的方式,直接调用range函数,那么程序肯定会崩溃,因为range(1,100000000)
函数直接产生一个从1-1亿的列表,这个列表中的所有数据都是存放在内存中的,会导致内存爆满。这时候可以采用生成器来解决这个问题,生成器不会一次性把所有数据都加载到内存中,而是在循环的时候临时生成的,循环一次生成一个,所以在程序运行期间永远都只会生成一个数据,从而大大节省内存。
解决打印1-1亿的问题:
用range
函数配合圆括号可以产生一个生成器:
# 普通的列表
num_list = [x for x in range(1,100000000)]
print(type(num_list))
# 生成器
num_gen = (x for x in range(1,100000000))
print(type(num_gen))
生成器运行原理:
生成器可以通过函数产生。如果在一个函数中出现了yield
表达式,那么这个函数将不再是一个普通的函数,而是一个生成器函数。yield
一次返回一个结果,并且会冻结当前函数的状态。
用生成器解决1-1亿的问题:
- 使用圆括号的生成器:
ret = (x for x in range(1,100000000)) print(type(ret))
- 使用函数:
def my_gen(start,end): index = start while index <= end: yield index index += 1 # 生成器有两个身份:迭代器和可迭代的对象 ret = my_gen(1,100000000) for x in ret: print(x)
生成器其实是迭代器也是可迭代对象:
因为生成器是迭代器也是可迭代的对象,那么可以通过for
循环进行遍历。并且因为它自身集成了迭代器和可迭代对象两个身份,因此它能被遍历一次。
next方法:
next
函数可以迭代生成器的返回值
生成器可以通过函数产生。如果在一个函数中出现了yield
表达式,那么这个函数将不再是一个普通的函数,而是一个生成器函数。yield
一次返回一个结果,并且会冻结当前函数的状态。以下是一个非常简单的生成器:
def my_gen():
yield 1
yield 2
yield 3
那么想要获取里面的值,可以通过以下方式获取:
ret = my_gen()
print(next(ret))
print(next(ret))
print(next(ret))
send方法:
send
方法和next
方法类似,可以用来触发生成器的下一个yield
,但是send
不仅可以触发下一个yield
,还可以发送数据过去,作为yield
表达式的值。- 如果用
send
方法执行刚刚开始的生成器,那么应该传None
给send
方法。不传或者传其他值都会报错。
def my_gen(start):
while start < 10:
temp = yield start
print(temp)
start += 1
ret = my_gen(1)
# next(ret)返回1
# 以下结果会打印1
print(next(ret))
# send("Hello World")返回2
# 但是会将"Hello World"赋值给yield start
# 因此temp = "Hello World"
# 那么会打印"Hello World"
# 并且打印2
print(ret.send('Hello World'))
send方法和next函数的区别:
- send方法可以传递值给
yield
表达式,而next
不可以。 - 在第一次执行生成器代码的时候,send函数必须要传一个None进去,而next函数可以不用传。
生成器中的return语句会触发StopIterator异常:
def my_gen(start):
while start < 3:
temp = yield start
# print(temp)
start += 1
# 触发StopIterator异常,会导致不能继续遍历
return "nihao"
ret = my_gen(1)
for x in ret:
print(x)
生成器的使用案例:
- 用
yield
实现斐波拉契数列:
斐波拉契数列的算法:除第一个和第二个数以外,任意一个数都可由前两个数相加得到:1,1,2,3,5,8,13,21,34,55…
用yield做多任务切换:
def neteasy_music(duration):
c_time = 0
while c_time < duration:
print('播放音乐 %d分钟'%c_time)
yield None
c_time += 1
def youku_movie(duration):
c_time = 0
while c_time < duration:
print('播放电影 %d分钟' % c_time)
yield None
c_time += 1
def main():
music = neteasy_music(10)
qq = youku_movie(10)
music_stop = False
movie_stop = False
while True:
try:
next(music)
except StopIteration:
print('音乐播放完毕')
music_stop = True
try:
next(qq)
except StopIteration:
print('电影播放完毕')
movie_stop = True
if music_stop and movie_stop:
break
用yield
做一个简单的协程:
from urllib.request import urlretrieve
def Consumer(url):
while True:
if url:
filename = url.split('/').pop()
url = yield urlretrieve(url,filename='images/'+filename)
print('consumer')
def Producter(consumer):
# 先执行一下,让后面的send不会报错
index = 2
next(consumer)
while index < 10:
print('生产者已经获取了第%d个图片链接' % index)
url = 'http://127.0.0.1:9000/%d.jpg' % index
consumer.send(url)
print('producter')
index += 1
consumer.close()
con = Consumer("http://127.0.0.1:9000/1.jpg")
Producter(con)