小知识:yield from 表达式

325 阅读2分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

先前我们介绍了协程send函数的使用。

除了send之外,python还添加了一些有助于协程使用的功能。比如yield from 这个表达式。

它长这样:

yield from xxxx

xxx是可迭代对象。 从最简单的意义上来讲,它的效果相当于这样:

for i in xxxx:
  yield i

也就是把整个xxxx这个可迭代对象中的每一项都生成出去。 举个例子

def gen():
  yield from range(5)
  yield 2
  yield from range(5)
# [0, 1, 2, 3, 4, 2, 0, 1, 2, 3, 4] 使用list(gen(n))会得到 这样的结果

所有的通过yield from 调用迭代器的结果,都会像先前写的for循环一样被当作gen函数产出的值,交由外部使用。

不过我们先前也说了,这个东西实际上是为协程准备的。

yield from 的后面是可以写一个协程的,当然协程或者说生成器本身就是迭代器,所以这是肯定的。

但并不只是这样,真正的好处在于它可以起到一种中介的作用。以上面的函数为例,对于外部,yield from 的值可以被当作gen函数的产出值,而反过来,对于yield from后的协程函数,外部的操作可以被转接到内层生成器上。

我们换一个例子,假设使用我们之前写过的统计奇数和偶数的协程, 然后通过yield from使用

def count():
    even = 0
    odd = 0
    while True:
        cur = yield even, odd
        print("#",cur)
        if cur % 2 == 0:
            even += 1
        else:
            odd += 1
def gen():
  yield from count()

像先前一样测试一下

    corutine = gen() 
    next(corutine)
    while True:
        x = int(input())
        print(corutine.send(x))

可以发现的是,协程可以以相同的效果执行。在yield from表达式运行时,可以当作我们就是直接对后面的协程操作。

如果我们想手动做这些操作的话,其实并不一定很好做,姑且先只考虑最简单的send 转发,可能得像下面这么写

def gen():
    cur = yield 
    sub = count()
    try: 
      while True:
         cur = yield sub.send(cur)
    except:
       pass

但是,更令人麻烦的是,实际上协程还有其他的操作函数,像throw之类的。

而yeild from 正是为我们解决这些痛点,当我们想要把一些操作抽取到另一个方法中,但是调用就需要舍弃特性,或者手动的做中介处理。

而yeild from避免了这种情况。它可以让我们的代码就好像是内联在原来的外部协程上一样,不用太多心智负担。