PEP 234 中有很多关于迭代器的说明。首先,与迭代器相关的一些概念需要先明确一下,像可迭代对象和迭代器对象。迭代就不用说什么了,循环的一种。那么,可迭代对象的定义就呼之欲出了。可以用于for循环的对象就是可迭代对象。那么,python中有哪些内建的可迭代对象呢?像python中的字符串,列表,元组,集合以及字典等都是可迭代对象。
那么,接下来就是迭代器对象了。一个list对象是可迭代的,那么,可用for循环遍历。但是,它还不是一个迭代器对象。可以使用iter方法将其变为迭代器对象,然后使用next方法对其进行迭代。
那么,为什么需要迭代器呢?查看的一些资料大都是说,使用迭代器可以实现惰性加载,减少内存使用,优化程序。此话怎讲?看个例子试试。
假如我们要实现一个生成序列的方法,我们大可以像下面这样写:
def lst(n):
num, nums = 0, []
while num < n:
nums.append(num)
num += 1
return nums
代码也很简单,就是根据所提供参数生成一个相关列表。这样貌似也很正规。有一个问题就是,如果你需要生成一个很多数的列表呢?那么,这个列表中包含的数越多,它占用的内存也就越多。那么,能不能有一种方式,实现这种生成数据的功能又不占用那么多内存呢?有,迭代器就可以。来看一看迭代器是怎么做的:
1 # -*- coding: utf-8 -*-
2 class it(object):
3 def __init__(self, n):
4 self.n = n
5 self.num, self.nums = 0, []
6
7 def __iter__(self):
8 return self
9
10 # 兼容python3
11 def __next__(self):
12 return self.next()
13
14 def next(self):
15 if self.num < self.n:
16 cur, self.num = self.num, self.num+1
17 return cur
18 else:
19 raise StopIteration()
这个貌似看不出来什么,其实,迭代器就是这样每次使用迭代器的时候都是调用的迭代器对象的内部next(__next__)方法。
这样也算是一次优化了,不过,问题就是这个优化的代码并不简洁哪。我就是想生成一个可循环的占用内存少的对象而已。这一下多出来这么多代码,写着不爽,看着也不舒服。还有没有更进一步的简化呢?有,生成器就可以干这事。
PEP 255 中有关于生成器的说明。生成器是一种特殊的迭代器,通过yield关键字来生成。包括生成器函数与生成器表达式。那么,我们用生成器函数来改写上面的例子:
1 # -*- coding: utf-8 -*-
2 def gen(n):
3 num = 0
4 while num < n:
5 yield num
6 num += 1
这样看着就清爽多了。gen 是一个生成器函数,调用 gen 函数之后会生成一个生成器对象。这个时候,就能像迭代器一样用生成器了。比如next方法,StopIteration异常。那么,对于这种打一鞭子走一步的对象来说,它必须得知道自己上一步走到哪了,这样你再鞭它的时候它才知道从哪开始。对于生成器来说,就是yield那个地方了。每执行一次就到yield那块,然后咔一下,返回一次值,再执行的时候,又从咔的地方开始继续下去了。
其实,生成器的内容还很多,不光是代替迭代器这一块。在python中,像协程这一块就少不了生成器。
上面的例子取自 python wiki 中关于生成器的说明。上面从列表讲到迭代器,再到生成器,给我的启发还是很大的。下面,写了一段代码看一下使用列表与使用迭代器(或者生成器)的效率问题:
import time
import sys
def gen(n):
num = 0
while num < n:
yield num
num += 1
def total_lst(func, n):
start = time.time()
nums = func(n)
space = sys.getsizeof(nums)
result = sum(nums)
stop = time.time()
print '%s after %ss result is %s and space is %s bytes' % (func.__name__, stop - start, result, space)
if __name__ == '__main__':
total_lst(range, 10000001)
total_lst(xrange, 10000001)
total_lst(gen, 10000001)
附一张运行效果图:
结果很明显,使用迭代器或者生成器的空间利用效率还是很高的。另外,内建生成器函数总是很高效的,像上面那个内建的xrange明显要比gen快了好多,比range 也要快,不仅空间利用率高,时间效率也明显哪。