迭代器(Iterator)
迭代的概念
通过for循环来逐个获取可迭代对象的每一项的过程就称为迭代(Iteration)。
#简单的迭代:
for i in [1,2,3]:
print(i)
#1
#2
#3
可迭代对象
可以直接作用于for循环的对象统称为可迭代对象(Iterable)。
有以下几种:
- 集合数据类型,如list、tuple、dict、set、str等;
- generator,包括生成器和带yield的generator function。
我们可以使用isinstance()判断一个对象是否是Iterable对象:
from collections import Iterable
print(isinstance([1,2], Iterable)) #True
print(isinstance((1,2), Iterable)) #True
print(isinstance({1:2,2:4}, Iterable)) #True
print(isinstance("hello", Iterable)) #True
print(isinstance(9999, Iterable)) #False
print(isinstance((x for x in range(5)), Iterable)) #True
迭代器
可以被next()函数调用并不断返回下一个值的对象称为迭代器(Iterator)。
Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
注意:list、dict、str等可迭代对象(Iterable)需要使用iter()函数来变成迭代器(Iterator),而所有的生成器都是迭代器(Iterator)
生成器(Generator)
通过上面的介绍,我们知道生成器(Generator)就是迭代器(Iterator)的一种,因此它同样具有惰性,即生成器是在循环中不断地生成新的元素,而不是一次性将所有的元素全部生成再等待被循环。
生成器的这种惰性,在某种情况下能给我们带来极大的便利。你可以试想一下,如果我们通过列表生成式创建一个包含1万个元素的列表,这将会一次性占用内存中存放这足足1万个元素的空间。如果我们通过生成器来创建呢,最开始我们得到的是只是一个生成器对象,接下来使用next()函数调用它一次,它才会生成下一个元素,而且占用的内存始终为1个元素的空间。
生成器中保存的不是要生成的所有值,而是算法,是生成元素的规则。每次被next()函数调用,生成器就计算出下一个元素的值,直到抛出StopIteration的错误,表示无法继续返回下一个值了。
下面生成容量为6的列表用于验证:
L = [x * x for x in range(6)] #创建一个列表对象
print(L) #[0, 1, 4, 9, 16, 25]
G = (x * x for x in range(6)) #创建一个生成器对象,只需把生成式的[]改成()
print(G) #<generator object <genexpr> at 0x000F544409E8>
print(next(G)) # 0
print(next(G)) # 1
print(next(G)) # 4
print(next(G)) # 9
print(next(G)) # 16
print(next(G)) # 25
print(next(G)) # 报错:StopIteration
我们可以通过for循环来迭代生成器,而且无需处理StopIteration的错误:
for i in (x * x for x in range(6)):
print(i)
# 0
# 1
# 4
# 9
# 16
# 25
现在我们知道,要创建一个生成器,只需要把一个列表生成式的[]改成()即可。
但是生成器给你的惊喜远不止如此,我们可以在一个函数中包含yield关键字,那么这个函数就不在是个普通函数,而是一个更强大的生成器。
下面是实现了一个普通的函数,可以输出斐波拉契数列的前n项:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b) #输出当前项
a, b = b, a + b
n = n + 1
return 'done'
fib(4)
#1
#1
#2
#3
#done
将print改为yield后,该函数就变成了生成器:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b #下面会介绍
a, b = b, a + b
n = n + 1
return 'done'
f = fib(4)
print(f) #<generator object fib at 0x0007E59678>
for i in f:
print(i)
#1
#1
#2
#3
神奇的yield:
要理解yield你必须先理解当你第一次调用函数的时候,函数里的代码并没有运行,函数返回的仅仅是一个生成器。
生成器和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成了生成器之后,在每次调用next()的时候才会执行一次并且遇到yield语句返回,再次执行时直接从上次返回的yield语句处继续执行。
下面有个例子可以帮助理解:
#实现一个可以返回生成器的函数
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)
#运行到此处的时候,生成器就被认为变成了空
f = odd()
print(next(f))
# step 1
# 1
print(next(f))
# step 2
# 3
print(next(f))
# step 3
# 5
print(next(f))
# 由于生成器下一项为空了,所以报错 :StopIteration
可以看到,每次调用该生成器都是从上次调用结束的地方开始执行。