Python的迭代器和生成器总结

225 阅读4分钟

可迭代对象 Iterable objects

实现可迭代协议的对象,称为可迭代对象。如list, tuple, dict, string等。

可迭代协议的定义是: 含__iter__()方法。且可迭代对象中的__iter__()方法返回的是一个对应的迭代器。

>>> from collections import Iterable
>>> help(Iterable)
Help on class Iterable in module _abcoll:
class Iterable(__builtin__.object)
 |  Methods defined here:
 |  
 |  __iter__(self)

我们可以用for语句,遍历一个可迭代对象:

>>> for i in [1, 2, 3, 4]:
...     print(i)
...
1
2
3
4

当然,还有很多有用的函数,他们接受的参数,就是可迭代对象,例如sum,join

>>> ",".join(("a", "b", "c"))
'a,b,c'

>>> sum((1,2,3))
6

迭代器 Iterator

满足迭代器协议的对象,称为迭代器对象。

迭代器协议约定如下:

  • __iter__()方法。且方法返回的Iterator对象本身
  • next()(python3是__next__())方法,每当next()方法被调用,返回下一个值,直到没有值可以访问,这个时候会抛出stopinteration的异常。
>>> from collections import Iterator
>>> help(Iterator)
Help on class Iterator in module _abcoll:
class Iterator(Iterable)
 |  Method resolution order:
 |      Iterator
 |      Iterable
 |      __builtin__.object
 |  
 |  Methods defined here:
 |  
 |  __iter__(self)
 |  
 |  next(self)
 |      Return the next item from the iterator. When exhausted, raise StopIteration

从上面的代码可以看出,Iterator继承自Iterabale

下面类MyRange是一个迭代器对象,是一个xrange的实现:

class MyRange(object):
	def __init__(self, end):
		self.end = end
		self.pivot = 0

	def __iter__(self):
		return self

	def next(self):
		if self.pivot < self.end:
			i = self.pivot
			self.pivot += 1
			return i
		else:
			raise StopIteration()

for i in MyRange(10):
	print i

可迭代对象和迭代器对象,通过函数iter转换。当调用函数iter函数时,就是调用Iterable对象的__iter__方法,它返回的是一个Iterator对象。

上面的for循环代码, 等价于:

my_range = MyRange(10)
iterator = iter(my_range) # 调用MyRange的__iter__方法
while True:
    try:
        print next(iterator)
    except StopIteration as e:
        break

为什么listdictstr等数据类型不是Iterator

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

如上面例子中,我们通过for遍历了整数0到9,但我们并没有实际构造从0到9的数组。这就是迭代器的魅力。

for i in MyRange(10):
	print i

生成器 generator

generator是Iterator的一种实现。他的出现,仅仅是为了便于编写Iterator。

Help on class generator in module __builtin__:

class generator(object)
 |  Methods defined here:
 |  
 |  __getattribute__(...)
 |      x.__getattribute__('name') <==> x.name
 |  
 |  __iter__(...)
 |      x.__iter__() <==> iter(x)
 |  
 |  __repr__(...)
 |      x.__repr__() <==> repr(x)
 |  
 |  close(...)
 |      close() -> raise GeneratorExit inside generator.
 |  
 |  next(...)
 |      x.next() -> the next value, or raise StopIteration
 |  
 |  send(...)
 |      send(arg) -> send 'arg' into generator,
 |      return next yielded value or raise StopIteration.
 |  
 |  throw(...)
 |      throw(typ[,val[,tb]]) -> raise exception in generator,
 |      return next yielded value or raise StopIteration.
 |  

从上面的定义中,可见,generator实现了Iterator必需的__iter__和next方法。

获得generator对象的一种方式,是把一个列表生成式的[]改成()

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

>>> for n in g:
...     print(n)
... 
0
1
4
9
16
25
36
49
64
81

获得generator对象的另一种方式,就是调用包含yield关键字的函数。即,如果一个函数定义中包含yield关键字,那么调用这个函数时,返回的是一个generator。

def foo():
... 	print("starting...")
... 	while True:
... 		res = yield 4
... 		print("res:",res)
... g = foo()
type(g)
<type 'generator'>

yield关键字,首先他拥有和return一样的功能,即在执行到yield时,程序返回某个值,返回之后不再往下运行。yield的第二个作用是,当对generator调用next时,从上次yield返回的地方开始执行。

我们用一段代码解释yield的执行顺序:


def foo():
    print "starting..."
    while True:
        res = yield 4
        print "res:",res
g = foo()
print next(g)
print next(g)

########## output #############
starting...
4
res: None
4

解释一下上面代码的执行属性:

  1. 程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)

  2. 直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环

  3. 程序遇到yield关键字,然后把yield想想成return,return了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(g))的结果

  4. 又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,所以接着下面的输出就是res:None,

  5. 程序会继续在while里执行,又一次碰到yield,这个时候同样return 出4,然后程序停止,print函数输出的4就是这次return出的4.

前面说过, generator的产生,是为了简化Iterator的实现。我们用generator实现前面的MyRange。

def my_range(n):
	i = 0
	while i < n:
		yield i
		i += 1

除此之外,generator还允许在迭代过程中,传递参数,用send实现。

def foo():
	print "starting..."
	while True:
		res = yield 4
		print "res:",res

g = foo()
print next(g)
print g.send(7)

########## output #############
starting...
4
res: 7
4

从上面的输出,可以理解到:用send的话,开始执行的时候,先接着上一次(return 4之后)执行,先把7赋值给了res,然后执行next的作用,遇见下一回的yield,return出结果后结束。

程序执行g.send(7),程序会从yield关键字那一行继续向下运行,send会把7这个值赋值给res变量

send的好处就是,在外层迭代时候,可以控制generator的遍历规则。