列表和迭代器区别
有些读者朋友,区分不开列表、字典、集合等非迭代器对象与迭代器对象,觉得迭代器是多余的。
先探讨它们的区别。首先,创建一个列表 a:
a = [1,3,5,7]
有没有朋友认为,列表就是迭代器的?注意列表 a 可不是迭代器类型(Iterator)。要想成为迭代器,需要经过内置函数 iter 包装:
a_iter = iter(a)
此时 a_iter 就是 Iterator,迭代器。可以验证:
In [21]: from collections.abc import Iterator
...: isinstance(a_iter,Iterator)
Out[21]: True
分别遍历 a、a_iter:
In [22]: for i in a:
...: print(i)
...:
1
3
5
7
In [23]: for i in a_iter:
...: print(i)
...:
1
3
5
7
打印结果一样,但是,再次遍历 a、a_iter 就会不同,a 正常打印,a_iter 没有打印出任何信息:
In [24]: for i in a:
...: print(i)
...:
1
3
5
7
In [25]: for i in a_iter:
...: print(i)
...:
这就是列表 a 和迭代器 a_iter 的区别:
列表不论遍历多少次,表头位置始终是第一个元素;
迭代器遍历结束后,不再指向原来的表头位置,而是为最后元素的下一个位置。
只有迭代器对象才能与内置函数 next 结合使用,next 一次,迭代器就前进一次,指向一个新的元素。
所以,要想迭代器 a_iter 重新指向 a 的表头,需要重新创建一个新的迭代 a_iter_copy
:
In [27]: a_iter_copy = iter(a)
调用 next,输出迭代器指向 a 的第一个元素:
In [28]: next(a_iter_copy)
Out[28]: 1
值得注意,我们无法通过调用 len 获得迭代器的长度,只能迭代到最后一个末尾元素时,才知道其长度。
那么,怎么知道迭代到元素末尾呢?我们不妨一直 next,看看会发生什么:
In [30]: next(a_iter_copy)
Out[30]: 3
In [31]: next(a_iter_copy)
Out[31]: 5
In [32]: next(a_iter_copy)
Out[32]: 7
In [33]: next(a_iter_copy)
StopIteration:
等迭代到最后一个元素后,再执行 next,会触发 StopIteration
异常。
所以,通过捕获此异常,就能求出迭代器指向 a 的长度,如下:
a = [1, 3, 5, 7]
a_iter_copy2 = iter(a)
iter_len = 0
try:
while True:
i = next(a_iter_copy2)
print(i)
iter_len += 1
except StopIteration:
print('iterator stops')
print('length of iterator is %d' % (iter_len,))
打印结果:
1
3
5
7
iterator stops
length of iterator is 4
以上总结:遍历列表,表头位置始终不变;遍历迭代器,表头位置相应改变;next 函数执行一次,迭代对象指向就前进一次;StopIteration 触发时,意味着已到迭代器尾部。
认识到迭代器和列表等区别后,我们再来说说生成器。
带 yield
的函数是生成器,而生成器也是一种迭代器。所以,生成器也有上面那些迭代器的特点。前些天已经讨论过生成器的一些基本知识,今天主要讨论生成器带来哪些好处,实际的使用场景在哪里。
节省内存案例
求一组数据累积乘,比如:三个数 [1,2,3],累积乘后返回 [1,2,6]。
一般的方法: