本文发自 http://www.binss.me/blog/learning-the-iterator-and-generator-of-python/,转载请注明出处。

欲学协程,先打基础。本文总结了Python中两个有用组件——生成器和迭代器。

迭代器(iterator)

通过列表生成式,我们可以直接创建一个列表。但是,列表容量受限且容易产生浪费。因此创造了迭代器。

迭代器是一个可迭代的对象,可以在循环(使用)的过程中才进行推算,反映了惰性求值的思想。

迭代器必须实现__next__()(注:此处为python3, python2应实现next())和__iter__()

__next__用于返回每次迭代时产生的值,当没有值时,需要手动抛出StopIteration。

__iter__用于返回可迭代对象(自己),在for...in时会被调用。当你使用isinstance(iterator, collections.Iterable)时,判断的也是对象是否有__iter__函数。

class fib():
    def __init__(self, limit):
        self.limit = limit
        self.a, self.b = 0, 1

    def __next__(self):
        self.a, self.b = self.b, self.b + self.a
        if self.a > self.limit:
            raise StopIteration
        else:
            return self.a

    def __iter__(self):
        return self

>>> fib()
<__main__.fib object at 0x7f8de29492e8>

使用姿势:

  1. next(itrator)

  2. for x in iterator

生成器(generator)

生成器同样是可迭代的,因此每个生成器都是一个迭代器。相比迭代器,生成器定义更加方便,只需定义一个函数然后不断yield即可。

例:

  1. 通过列表生成式生成:

     >>> (x * x for x in range(10))
      at 0x7f3d76296e08>
  2. 定义一个包含yield关键字的函数,调用该函数返回生成器

     def fib():
         a, b = 0, 1
         while True:
             yield b
             a, b = b, a+b
    
     >>> fib()

使用姿势:

  1. next(generator)

    生成器在每次调用next()的时取得控制权,从上个yield处继续执行函数,直到遇到yield时返回,返回值为yield后跟的表达式的值。

    若已计算到最后一个元素(没有更多的元素)或遇到return时,抛出StopIteration。

  2. generator.send()

    PEP 342新增,用于向生成器传参,作为yield表达式的右值。如:

     value = yield b

    通过fib.send(10),我们可以在将控制权交回生成器的同时把值传进去,此时value = 10。

    send()函数返回值同next,都是返回下一个yield后跟的表达式的值。当然send也可以用来启动一个新的(没有yield过的)生成器,使用 send(None) 即可。

  3. for x in generator

    生成直到没有更多元素时结束,x为每轮迭代生成器返回的值。

扩展

产生一个生成器永远是异步的,即使产生生成器的函数中含有阻塞代码,在产生生成器时,函数的参数会绑定到生成器,结果返回是一个生成器,类型是generator,不会去执行函数中的代码。只有当生成器在调用时(通过next或send),函数才真正取得控制权而执行。因此我们发现生成器已经具备了协程的一些必备条件,如:

  • 能够暂停执行,保存状态(yield)
  • 能够恢复执行(next、send)

但是生成器还不足以能成为协程。因为生成器在暂停执行、交出控制权后,无法感知到调用的就绪,从而重新恢复执行、取回控制权。

比如:

response = yield http_client.fetch("http://example.com")
...

我们在生成器函数中向HTTP server发出一个请求后通过yield将控制权交出去,期待能在收到server响应时得到结果,并恢复执行。然而生成器并不能自动恢复执行。因此需要一套附加的机制来做这件事情:它监听外部响应(socket可读),读取响应结果并将结果通过send()发送给生成器,从而让生成器恢复执行。

所以接下来我会学习并分析下一些Python中的协程实现。

参考

http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python