先说迭代器,对于string
、list
、dict
、tuple
等这类容器对象,使用for
循环遍历是很方便的。在后台for
语句对容器对象调用iter()
函数,iter()
是python的内置函数。iter()
会返回一个定义了next()
方法的迭代器对象,它在容器中逐个访问容器内元素,next()
也是python的内置函数。在没有后续元素时,next()
会抛出一个StopIteration
异常,通知for
语句循环结束。好比:python
>>> s = 'abc' >>> it = iter(s) >>> it <str_iterator object at 0x7f71fefe9d68> >>> next(it) 'a' >>> next(it) 'b' >>> next(it) 'c' >>> next(it) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
上面说的都是python自带的容器对象,它们都实现了相应的迭代器方法,那若是是自定义类须要遍历怎么办?方法很简单,对这个类AClass,实现一个__iter__(self)
方法,使其返回一个带有__next__(self)
方法的对象就能够了。若是你在AClass恰好也定义了__next__(self)
方法(通常使用迭代器都会定义),那在__iter__
里只要返回self
就能够。废话少说,先上代码:函数
class Fib(object): def __init__(self, max): super(Fib, self).__init__() self.max = max def __iter__(self): self.a = 0 self.b = 1 return self def __next__(self): fib = self.a if fib > self.max: raise StopIteration self.a, self.b = self.b, self.a + self.b return fib def main(): fib = Fib(100) for i in fib: print(i) if __name__ == '__main__': main()
简单讲下代码会干什么,定义了一个Fib类,用于生成fibonacci序列。用for遍历时会逐个打印生成的fibonacci数,max是生成的fibonacci序列中数字大小的上限。工具
在类的实现中,定义了一个__iter__(self)
方法,这个方法是在遍历时被iter()
调用,返回一个迭代器。由于在遍历的时候,是直接调用的python内置函数iter()
,由iter()
经过调用__iter__(self)
得到对象的迭代器。有了迭代器,就能够逐个遍历元素了。而逐个遍历的时候,也是使用内置的next()
函数经过调用对象的__next__(self)
方法对迭代器对象进行遍历。因此要实现__iter__(self)
和__next__(self)
。并且由于实现了__next__(self)
,因此在实现__iter__(self)
的时候,直接返回self就能够。code
为了更好理解,我再简单重复下上面说的那一段:在循环遍历自定义容器对象时,会使用python内置函数iter()
调用遍历对象的__iter__(self)
得到一个迭代器,以后再循环对这个迭代器使用next()
调用迭代器对象的__next__(self)
。__iter__
只会被调用一次,而__next__
会被调用 n 次。对象
下面说生成器。内存
生成器(Generator)是建立迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在须要返回数据的时候使用
yield
语句。每次next()
被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和全部的数据值)。如下示例演示了生成器能够很简单的建立出来:ci
>>> def reverse(data): ... for index in range(len(data)-1, -1, -1): ... yield data[index] ... >>> for char in reverse('hello'): ... print(char) ... o l l e h
关于迭代器和生成器的区别,生成器能作到迭代器能作的全部事,并且由于自动建立了__iter__()
和 next()
方法,生成器显得特别简洁,并且生成器也是高效的。除了建立和保存程序状态的自动方法,当发生器终结时,还会自动抛出StopIteration
异常。一个带有yield
的函数就是一个 生成器,它和普通函数不一样,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用next()
(在 for 循环中会自动调用next()
)才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个yield
语句就会中断,并返回一个迭代值,下次执行时从yield
的下一个语句继续执行。看起来就好像一个函数在正常执行的过程当中被yield
中断了数次,每次中断都会经过yield
返回当前的迭代值(yield
暂停一个函数,next()
从其暂停处恢复其运行)。generator
另外对于生成器,python还提供了一个生成器表达式:相似与一个yield
值的匿名函数。表达式自己看起来像列表推到, 但不是用方括号而是用圆括号包围起来:string
>>> unique_characters = {'E', 'D', 'M', 'O', 'N', 'S', 'R', 'Y'} >>> gen = (ord(c) for c in unique_characters) >>> gen <generator object <genexpr> at 0x7f2be4668678> >>> for i in gen: ... print(i) ... 69 79 83 77 82 78 89 68 >>>
若是须要,能够将生成器表达式传给tuple
、list
或是set
来迭代全部的值而且返回元组、列表或是集合。在这种状况下,不须要一对额外的括号 ———— 直接将生成器表达式 ord(c) for c in unique_characters
传给tuple()
等函数就能够了, Python 会推断出它是一个生成器表达式。it
最后,为何要使用生成器?由于效率。使用生成器表达式取代列表解析能够同时节省 cpu 和 内存(ram)。若是你构造一个列表的目的仅仅是传递给别的函数,(好比 传递给tuple()
或者set())
, 那就用生成器表达式替代吧!