python迭代器与生成器详解
例子 老规矩,先上一个代码: def add(s,x): return s + x def gen(): for i in range(4): yield i base = gen() for n in [1,10]: base = (add(i,n) for i in base) print list(base) 这个东西输出可以脑补一下, 结果是[20,21,22,23],而不是[10,11,12,13]。 当时纠结了半天,一直没搞懂,后来齐老师稍微指点了一下, 突然想明白了--真够笨的,唉。。好了--正好趁机会稍微小结一下python里面的生成器。 迭代器(iterator) 要说生成器,必须首先说迭代器 itertion: 就是迭代,一个接一个(one after another),是一个通用的概念,比如一个循环遍历某个数组。 In [3]: s = 'hi' In [4]: s.__getitem__ Out[4]: <method-wrapper '__getitem__' of str object at 0x7f9457eed580> In [5]: s.next # 没有next方法 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-5-136d3c11be25> in <module>() ----> 1 s.next AttributeError: 'str' object has no attribute 'next' In [6]: l = [1,2] # 同理 In [7]: l.__iter__ Out[7]: <method-wrapper '__iter__' of list object at 0x7f945328c320> In [8]: l.next --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-8-c6f8fb94c4cd> in <module>() ----> 1 l.next AttributeError: 'list' object has no attribute 'next' In [9]: iter(s) is s #iter() 没有返回本身 Out[9]: False In [10]: iter(l) is l #同理 Out[10]: False 但是对于iterator则不一样如下,另外iterable可以支持多次迭代,而iterator在多次next之后,再次调用就会抛异常,只可以迭代一次。 In [13]: si = iter(s) In [14]: si Out[14]: <iterator at 0x7f9453279dd0> In [15]: si.__iter__ # 有__iter__ Out[15]: <method-wrapper '__iter__' of iterator object at 0x7f9453279dd0> In [16]: si.next #拥有next Out[16]: <method-wrapper 'next' of iterator object at 0x7f9453279dd0> In [20]: si.__iter__() is si #__iter__返回自己 Out[20]: True 这样,由这几个例子可以解释清楚这几个概念的区别。 自定义iterator 与数据分离 说到这里,迭代器对象基本出来了。下面大致说一下,如何让自定义的类的对象成为迭代器对象,其实就是定义__iter__和next方法: In [1]: %paste class DataIter(object): def __init__(self,*args): self.data = list(args) self.ind = 0 def __iter__(self): #返回自身 return self def next(self): # 返回数据 if self.ind == len(self.data): raise StopIteration else: data = self.data[self.ind] self.ind += 1 return data ## -- End pasted text -- In [9]: d = DataIter(1,2) In [10]: for x in d: # 开始迭代 ....: print x ....: 1 2 In [13]: d.next() # 只能迭代一次,再次使用则会抛异常 --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) ----> 1 d.next() <ipython-input-1-c44abc1904d8> in next(self) 10 def next(self): 11 if self.ind == len(self.data): ---> 12 raise StopIteration 13 else: 14 data = self.data[self.ind] 从next函数中只能向前取数据,一次取一个可以看出来,不过不能重复取数据,那这个可不可以解决呢? 我们知道iterator只能迭代一次,但是iterable对象则没有这个限制,因此我们可以把iterator从数据中分离出来,分别定义一个iterable与iterator如下: class Data(object): # 只是iterable:可迭代对象而不iterator:迭代器 def __init__(self,*args): self.data = list(args) def __iter__(self): # 并没有返回自身 return DataIterator(self) class DataIterator(object): # iterator: 迭代器 def __init__(self,data): self.data = data.data self.ind = 0 def __iter__(self): return self def next(self): if self.ind == len(self.data): raise StopIteration else: data = self.data[self.ind] self.ind += 1 return data if __name__ == '__main__': d = Data(1,2,3) for x in d: print x,for x in d: print x, 输出就是: 1,3 In [8]: sys.getsizeof(range(1000000)) Out[8]: 8000072 In [9]: sys.getsizeof(xrange(1000000)) Out[9]: 40 另外有个小tips, 就是为什么可以使用for 迭代迭代器对象,原因就是for替我们做了next的活,以及接收StopIteration的处理。 迭代器大概就记录到这里了,下面开始一个特殊的更加优雅的迭代器: 生成器 生成器(generator) 首先需要明确的就是生成器也是iterator迭代器,因为它遵循了迭代器协议. 两种创建方式 包含yield的函数 生成器函数跟普通函数只有一点不一样,就是把 return 换成yield,其中yield是一个语法糖,内部实现了迭代器协议,同时保持状态可以挂起。如下: def gen(): print 'begin: generator' i = 0 while True: print 'before return ',i yield i i += 1 print 'after return ',i a = gen() In [10]: a #只是返回一个对象 Out[10]: <generator object gen at 0x7f40c33adfa0> In [11]: a.next() #开始执行 begin: generator before return 0 Out[11]: 0 In [12]: a.next() after return 1 before return 1 Out[12]: 1 首先看到while True 不必惊慌,它只会一个一个的执行~ 调用gen()并没有真实执行函数,而是只是返回了一个生成器对象 In [15]: def func(): ....: print 'begin' ....: for i in range(4): ....: yield i In [16]: a = func() In [17]: list(a) #检索数据,开始执行 begin Out[17]: [0,1,3] yield还有其他高级应用,后面再慢慢学习。 生成器表达式 列表生成器十分方便:如下,求10以内的奇数: 同样在python 2.4也引入了生成器表达式,而且形式非常类似,就是把[]换成了(). In [18]: a = ( i for i in range(4)) In [19]: a Out[19]: <generator object <genexpr> at 0x7f40c2cfe410> In [20]: a.next() Out[20]: 0 可以看出生成器表达式创建了一个生成器,而且生有个特点就是惰性计算,只有在被检索时候,才会被赋值。 def multipliers(): return (lambda x : i * x for i in range(4)) #修改成生成器 print [m(2) for m in multipliers()] 这个就是说,只有在执行m(2)的时候,生成器表达式里面的for才会开始从0循环,然后接着才是i * x,因此不存在那篇文章中的问题。 惰性计算这个特点很有用,上述就是一个应用,2gua这样说的: 性计算想像成水龙头,需要的时候打开,接完水了关掉,这时候数据流就暂停了,再需要的时候再打开水龙头,这时候数据仍是接着输出,不需要从头开始循环 回到例子 看到这里,开始的例子应该大概可以有点清晰了,核心语句就是: for n in [1,n) for i in base) 在执行list(base)的时候,开始检索,然后生成器开始运算了。关键是,这个循环次数是2,也就是说,有两次生成器表达式的过程。必须牢牢把握住这一点。 生成器返回去开始运算,n = 10而不是1没问题吧,这个在上面提到的文章中已经提到了,就是add(i,n)绑定的是n这个变量,而不是它当时的数值。 然后首先是第一次生成器表达式的执行过程:base = (10 + 0,10 + 1,10 + 2,10 +3),这是第一次循环的结果(形象表示,其实已经计算出来了(10,3)),然后第二次,base = (10 + 10,11 + 10,12 + 10,13 + 10),终于得到结果了[20,23]. 具体执行过程可以在pythontutor上手动看看执行过程。 小结 概括 1.iterable,iterator与itertion的概念 其实这一块, 那几个概念搞清楚,,这个很关键, 搞懂了后面就水到渠成了。而且对之前的知识也有很多加深。 参考 http://www.shutupandship.com/2012/01/understanding-python-iterables-and.html (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |