本文发自 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>
使用姿势:
-
next(itrator)
-
for x in iterator
生成器(generator)
生成器同样是可迭代的,因此每个生成器都是一个迭代器。相比迭代器,生成器定义更加方便,只需定义一个函数然后不断yield即可。
例:
-
通过列表生成式生成:
>>> (x * x for x in range(10)) at 0x7f3d76296e08>
-
定义一个包含yield关键字的函数,调用该函数返回生成器
def fib(): a, b = 0, 1 while True: yield b a, b = b, a+b >>> fib()
使用姿势:
-
next(generator)
生成器在每次调用next()的时取得控制权,从上个yield处继续执行函数,直到遇到yield时返回,返回值为yield后跟的表达式的值。
若已计算到最后一个元素(没有更多的元素)或遇到return时,抛出StopIteration。
-
generator.send()
PEP 342新增,用于向生成器传参,作为yield表达式的右值。如:
value = yield b
通过fib.send(10),我们可以在将控制权交回生成器的同时把值传进去,此时value = 10。
send()函数返回值同next,都是返回下一个yield后跟的表达式的值。当然send也可以用来启动一个新的(没有yield过的)生成器,使用 send(None) 即可。
-
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