这一part专讲yield from 语法。在前一篇文章中,我们实现了一个协程版的生产者消费者,在上一篇文章中,需要使用异常机制来返回消费者函数的值。这种写法是非常讨厌的,在python中,可以使用yield from来简化这种写法。
in运算符
在python中,如果在对象的表现上需要实现一些新的动作,就会用到内置的魔法方法,也就是那些被双下划线包裹的方法。比如让自定义类型的对象实现加运算,只需要重写该自定义对象的__add__方法。
在python中除了+运算符这些常见的运算符,python还实现了in运算符。把这一运算符作为关键词在其他语言里很少见到。这一设计,使得判断“对象是否处于集合中”这种动作只需要一个if语句,而且还让for循环的可读性提升了一个level。而一个对象只要实现了迭代器相关的魔法方法,就支持in运算,协程也支持。
yield from新语法
在协程的调用方,一个简化版的调用往往长这样子:
for v in g:
yield v
而新语法做的一个工作就是将上述代码实现的功能用一个新语法yield from代替,最终能完成这样的操作:
RESULT = yield from EXPR
实现yield from语法的伪代码如下:
_i = iter(EXPR)
try:
_y = next(_i)
except StopIteration as _e:
_r = _e.value
else:
while 1:
try:
_s = yield _y
except StopIteration as _e:
_r = _e.value
break
RESULT = _r
上述代码只包含了send方法的处理,不包含其他方法和异常的处理。其所做的工作就是当遇到StopIteration 时,就把异常中的value作为结果返回。这一语法,既能将send发送的值从外部传递到内部,又能将返回值从内部传回外部。
理解了这一点,看下面这张图就很容易了,这张图的具体解释可以看fluent python第16章。这里简单的讲所完成的功能就是,通过yield from将子生成器的控制权,从子作用域传递到上层作用域。
这段代码包含三个部分。子生成器:一个用于计算移动平均值的函数,用yield接收send发送过来的值,return返回累加之后的结果。
委派生成器:一个while循环将yield from通道的结果保存到上层传入的results字典中,yield from是可暂停的。
调用方:next预激,send发送值,send(None)结束。
yield from的语法的出现不仅仅是为了简化for+in+yield的语法糖,实质上的作用是将生成器的控制权,也就是send等方法的调用与返回,在不同层次的作用域之间传递。而协程的关键点也在于控制权的让渡,这些花式玩法本质上是在折腾控制权这个事,具体而言就是决定让哪部分代码占用当前CPU。
基于协程的事件驱动型框架
对于一个事件驱动型框架而言,至少要有这三个东西外层要有个loop,负责各种底层操作的调度,具体的操作就是调用send这些方法。
中层是控制权传递的语法,yield到await都是在干这事。
底层有个有个协程函数,无论是Future还是Event对象,都能通过相应的方法让渡出控制权。
所有现在的各种框架,在最后执行协程的时候,多数操作都是将一个协程的函数体(而不是函数的实例),传递给一个loop执行,和map方法一样一样的。
协程的意义
协程这个东西可以看成用户控制的线程,这里的用户控制是区别于真实的由硬件控制并切换上下文的线程。协程可以以同步的方式写异步非抢占式代码,代码逻辑更人性化,没有新语法时,python中是靠tornado和gevent等库来实现这一目的。
如果不用协程,这种控制流一般用回调函数的方式来写。而在python这种靠缩进判断代码块的语言,用回调写起来真是要死要死。就算JavaScript,也不应该嵌套太多,不服你看下图。
至此,我们基本了解了yield from的前身今世,这个语法在python3.5被await关键字替代,经过这一系列的花式操作,python完成了对协程的原生支持。下一章讲await做个收尾,可能要等到很久之后了。
PS:
说实话,这一系列写下来我是不满意的。本意是想通过协程语法的改进来把有关知识点串起来,希望读者老爷们读下来能有一个恍然大悟的感觉,现在看来是达不到了。文章结构和内容都没达到我想要的目的,可能我理解是理解了,表达出来又是一回事。不过,在写的过程中我自己倒是又学到不少,括弧笑。总之还是先发出来,等以后水平上去了,再回过头来改吧。