由于生成器提供了再次进入一个函数体的机制,其实它已经可以当成协程来使用了。
写个很简单的例子:
这里的 coroutine 函数用于处理连接和收发数据,而 main 函数则等待读写和传递参数。虽然看上去和同步的调用没啥区别,但其实在 main 函数中可以同时执行多个 coroutine,以实现并发执行。
再说一下 yield from。
如果一个生成器内部需要遍历另一个生成器,并将数据返回给调用者,你需要遍历它并处理所遇到的异常;而用了 yield from 后,则可以一行代码解决这些问题。具体例子就不列出了,PEP 380 里有详细的代码。
这对于协程而言也是一个利好,这使得它的调用也得到了简化:
接下来就该轮到协程(coroutine)登场了。
从上文也可看出,调用 yield from gen 时,我无法判定我是遍历了一个生成器,还是调用了一个协程,这种混淆使得接口的设计者和使用者需要花费额外的工夫来约定和检查。
于是 Python 又先后添加了 asyncio.coroutine 和 types.coroutine 这两个装饰器来标注协程,这样就使得需要使用协程时,不至于误用了生成器。顺带一提,前者是 asyncio 库的实现,需要保持向下兼容,本文暂不讨论;后者则是 Python 3.5 的语言实现,实际上是给函数的 __code__.co_flags 设置 CO_ITERABLE_COROUTINE 标志。随后,async def 也被引入以用于定义协程,它则是设置 CO_COROUTINE 标志