本文中所有代码均运行在Python 2.7上
从迭代器说起
迭代器(Iterator),顾名思义,就是一个可供迭代(Iterables)的对象。
比如,在一个列表中,依次读取其中的元素,就是一个迭代的过程。简单来说,可以使用for ... in ...
语句迭代访问的对象,都是迭代器。其中包括list
, set
, string
, dict
, file
等等。
这种简洁的迭代方式非常简便易用,但同时也带来了一个问题,因为迭代器中所有的数据都被放在内存中以供使用,所以会对内存造成很大的压力。
举个栗子:
>>> for num in range(10**1000):
string = ' ' + ' '
....
OverflowError: range() result has too many items
也许在大多数场景中,我们不会去主动使用range()
进行如此大的一个迭代,但我们很有可能会通过open()
去打开一个大文件(可能是一个比较大的原始数据文件,或者一个大的log文件等),它的大小很有可能会大于运行环境的内存,这时候程序就会因为OverflowError
而崩溃退出。
试试生成器?
同样的,生成器具备迭代器的所有特性,但是它仅供迭代一次,因为它采用的惰性计算的优化策略,就是说它只有当被使用到的时候才把数据取出或者计算出,放到内存中,访问之后随机销毁。
在使用上,它和迭代器的主要区别在于仅能进行一次迭代访问。
>>> iterator = [x for x in range(7)]
>>> Iterator
[0, 1, 2, 3, 4, 5, 6]
>>> generator = (x for x in range(7))
( generator object (genexpr) at 0X00000000025DBA20)
总结
在多数情况下,我们操作的数据量都不足以撼动内存,那是否意味着迭代对我们已经完全够用,生成器完全无用呢?
非也,惰性求值不仅在内存层级对空间有着优化的效果,在计算时间上也有着一定的提高。由于避免了不必要的计算,节省了计算资源。
最后以一个迭代器和生成器之间的斐波那契函数对比结束。
#先来一个迭代版本
def fib(n):
a, b = 0, 1
if n<2:
return [a, b][n]
for i in range():
a, b = b, a+b
return b
#之后是生成器版本
def fibHelper():
a, b = 0, 1
while True:
yield a
a, b = b, a+b
def fib(n):
while n>0:
result = fibHelper()
n -= 1
return result