背景 链接到标题
在清理 Pocket 列表的时候,发现自己很早之前收藏过 dabeaz 在 2008 年 PyCon 关于生成器的 PPT 讲解,今天读完,有所收获。
在 PPT 中, dabeaz 通过一个具体的文件处理的例子,一步一步的讲解了程序的演进,具体代码可以在 Github 查看。
生成器 链接到标题
使用 yield 关键字的函数就是生成器。生成器在运行时生成值,所以只能迭代一次。生成器可以通过 next 关键字执行,通常我们通过 for 循环来迭代生成器,可以自动处理 StopIteration 情况。
一个简单的生成器例子:
def countdown(n):
while n > 0:
yield n
n -= 1
>>> for i in countdown(5):
... print(i, end=' ')
...
5 4 3 2 1
当我们调用生成器时,仅返回一个生成器对象,不会执行函数内容,只有当执行 __next__()
时函数才会真正执行。yield 会返回给调用者当前值,同时暂停执行,等待下一次调用 __next__()
继续执行。
协程 链接到标题
在 python 中通过生成器的方式来实现协程:
In [7]: def recv_count():
...: try:
...: while True:
...: n = yield
...: print("T-minus", n)
...: except GeneratorExit:
...: print("boom")
...:
In [8]: r = recv_count()
In [9]: r.send(None) # init
In [10]: r.next() # yield 未接收到调用者发送具体值
('T-minus', None)
In [13]: r.send(1)
('T-minus', 1)
In [14]: r.send(2)
('T-minus', 2)
In [15]: r.send(3)
('T-minus', 3)
关于为什么要执行 r.send(None)
,可以看 PEP-342 中具体解释:
Because generator-iterators begin execution at the top of the generator’s function body, there is no yield expression to receive a value when the generator has just been created. Therefore, calling send() with a non-None argument is prohibited when the generator iterator has just started, and a TypeError is raised if this occurs (presumably due to a logic error of some kind). Thus, before you can communicate with a coroutine you must first call next() or send(None) to advance its execution to the first yield expression.
在此基础上进行扩展,我们写一个生产消费者模型:
root@yiran-30-250:/tmp
$ cat a.py
#!/usr/bin/python3
def consumer():
r = ''
while True:
n = yield r
print('Consuming %s ...' % n)
r = '200 OK'
def producer(c):
c.send(None)
n = 0
while n < 5:
n += 1
print("Producing %s ..." % n)
r = c.send(n)
print("Consumer return: %s" % r)
c.close()
c = consumer()
producer(c)
root@yiran-30-250:/tmp
$ python3 a.py
Producing 1 ...
Consuming 1 ...
Consumer return: 200 OK
Producing 2 ...
Consuming 2 ...
Consumer return: 200 OK
Producing 3 ...
Consuming 3 ...
Consumer return: 200 OK
Producing 4 ...
Consuming 4 ...
Consumer return: 200 OK
Producing 5 ...
Consuming 5 ...
Consumer return: 200 OK
在 Python3.3 之后,添加了 yield from
, asyncio
等关键字,在之后的博客中单独记录。