1、生成器
生成器:如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
回顾列表推导式
my_list = [x for x in range(10)]
print(my_list) # 列表推导式:直接包含所有数据的列表
(1)第一种方法产生一个生成器:
my_generator = (x for x in range(10))
print(my_generator) # 生成器:一个生成器对象,包含算法
我们可以打印出列表,但是怎么输出generator的每个值呢?
# 通过系统的内建函数next()获取生成器中下一个数据
print(next(my_generator)) # 0
print(next(my_generator)) # 1
print(next(my_generator)) # 2
print(next(my_generator)) # 3
# (2)通过类型的 __next__()魔法方法,直接获取下一个数据
print(my_generator.__next__()) # 4
print(my_generator.__next__()) # 5
print(my_generator.__next__()) # 6
print(my_generator.__next__()) # 7
print(my_generator.__next__()) # 8
print(my_generator.__next__()) # 9
print(my_generator.__next__()) # StopIteration
我们讲过generator里保存的是算法,每次调用next(),就计算出my_generator的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
但是每次取值都使用next(),有点太麻烦,而且还要监听StopIteration异常,所以我们可以使用,for循环遍历。并且,当生成器没有元素是for循环自动停止。
for i in my_generator:
print(i,end=" ")
# 结果:0 1 2 3 4 5 6 7 8 9
(2)第二种方法产生一个生成器:
著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1,1,2,3,5,8,13,21……
斐波拉契数列用列表推导式是写不出来的,因为它是无穷无尽的,但是我们可以看的出来,它是有规则的,可以通过算法表达出来!
def fbi(max):
n,a,b = 0,0,1
while n < max:
print(b,end=" ") #end=" "让print()不再默认换行输出,而是空格输出
a,b = b,a + b
n += 1
return 'done'
fbi(10)
# 结果:1 1 2 3 5 8 13 21 34 55
此时的斐波拉列函数离生成器只有一步之遥!只要将print(b,end=” “)换成yield,就变成generator的函数
def fbi(max):
n,a,b = 0,0,1
while n < max:
yield b
a,b = b,a + b
n += 1
return 'done'
fb = fbi(10)
print(type(fb))
# 结果:<class 'generator'>
一般取generator的用for循环遍历,但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
while True:
try:
x = next(b)
print("b:",x)
except StopIteration as e:
print('Generator return value:', e.value)
break
# 结果:
b: 1
b: 1
b: 2
b: 3
b: 5
b: 8
b: 13
b: 21
b: 34
b: 55
Generator return value: done
2、迭代器
我们知道直接可以使用for循环遍历的数据有:
一类:list、tuple、set、dict和str等
还有一类:generator,包括生成器和带yield的生成器函数
这些可以直接使用for循环遍历的对象,统称为:可迭代对象(Iterabel)
可以使用类型判断函数isinstance()判断
import collections
my_set = {1,2,3,4,5}
my_tuple = (1,2,3,4,5)
my_dict = {1:"2",2:"3"}
my_list = (1,2,3,4)
my_generator = (x for x in range(10))
print(isinstance(my_tuple,collections.Iterable))
print(isinstance(my_set,collections.Iterable))
print(isinstance(my_dict,collections.Iterable))
print(isinstance(my_list,collections.Iterable))
print(isinstance(my_generator,collections.Iterable))
# 结果:
True
True
True
True
True
生成器不但可以被for遍历,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
同样也可以用isinstance()判断
print(isinstance(my_tuple,collections.Iterator))
print(isinstance(my_set,collections.Iterator))
print(isinstance(my_dict,collections.Iterator))
print(isinstance(my_list,collections.Iterator))
print(isinstance(my_generator,collections.Iterator))
# 结果:
False
False
False
False
True
生成器都是Iterator对象,但list、dict、str、set 、tuple虽然是Iterable,却不是Iterator。
把list、dict、str、tuple、set 等Iterable变成Iterator可以使用iter()函数:
print(isinstance(iter(my_tuple),collections.Iterator))
print(isinstance(iter(my_set),collections.Iterator))
print(isinstance(iter(my_dict),collections.Iterator))
print(isinstance(iter(my_list),collections.Iterator))
# 结果:
True
True
True
True
为什么list、set、tuple、dict等数据类型不是Iterator?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
3、小结
1、凡是可用于for循环的对象都是:Iterabel类型
2、凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
3、生成器与迭代器的区别:
生成器:用来产生数据的
迭代器:用来遍历数据的
本文参照了廖雪峰老师的文章,但也加入了自己的一些理解!