Python之yield、yield from(二)

一、yield协程

2001年,Python2.2引入了yield关键字实现生成器函数。而2006年,Python2.5又为生成器增加了额外的方法和功能,最重要的当属.send()方法。

与.__next__()方法一样,.send()方法致使生成器前进到下一个yield语句。不过,.send()方法还允许客户把数据发给生成器,.send()方法会把发送的值传给生成器中对应的yield表达的值。也就是说.send()方法允许客户代码和生成器之间双向交换数据。而__next__()方法只允许客户从生成器获取数据。

所以引出一个新的概念:通过.send()双向交换数据的生成器就是协程。而通过.__next__()单向获取数据的就是生成器。

协程是一个过程,这个过程通过.send()方法,实现与调用方相互协作。

我们来看《流畅的Python》上的一个例子:

def simple_coro2(a):
    print('-> Started: a =',a)
    b = yield a
    print('-> Received: b=',b)
    c = yield a + b
    print('-> Received: c=',c)

my_coro2 = simple_coro2(14)

如果我们在控制台运行,会得到以下结果:

next(my_coro2)
-> Started: a = 14
Out[7]: 14

在上面的程序中,yield a大概相当于return a的意思,如果我们把b=yield a改成b=yield,simple_coro2函数就会返回一个None。我们再执行:

my_coro2.send(28)
-> Received: b= 28
Out[8]: 42

 此时才会将28的值赋给变量b,返回a+b即42。再执行:

my_coro2.send(99)
-> Received: c= 99
Traceback (most recent call last):

  File "", line 1, in
    my_coro2.send(99)

StopIteration

 此时将99赋给变量c,打印消息,然后协程终止,抛出StopIteration异常。这个流程并不容易理解,请看图:

Python之yield、yield from(二)_第1张图片

程度经过了三个阶段,前面两次都是到yield为止,左边的赋值是第二次调用才执行的。

从上面的程序可以看出,

1,协程通过.next()启动,.send()发送数据。

2,生成器与协程都包含yield,但是协程的yield关键字出现在表达式的右边。

3,协程和普通函数调用不同之处在于,它是一个交互式的过程,有来有回,多次调用,且变量能保持最新的状态。

 

二、yield from协程

Python3.3才引入yield from结构。yield from要比yield复杂,因为它的作用要比yield多得多。我们总结下yield from结构的基本特点:

1,替代嵌套for循环,调用iter(),获取迭代器(前一篇博客已讲)。

2,调用协程时,会自动预激活协程(相当于执行了一次next()方法)。

3,yield协程一般需要调用多次.send(),获取多个结果(通过yield返回值)。而yield from只需要获取最一次执行的结果(协程执行完毕后,返回StopIteration.value)。

4,第3步中,yield from会自动捕获StopIteration异常。子生成器中止后,将return value赋给StopIteration.value,然后抛出StopIteration异常。然后当前yield from表达式会替换成子生成器中返回的StopIteration.value。而yield需要手动处理异常,并手动返回值。

来一个简单的例子,统计学生的平均成绩:

from collections import namedtuple

Student = namedtuple('Student','name average_score')

data = {'张三':[70,80,90],
        '李四':[75,80,88],
        '王五':[80,87,90]}

def averager():
    total = 0.0
    count = 0
    average_score = None
    while True:
        score = yield
        if score is None:
            break
        total +=score
        count +=1
        average_score = total/count
    return average_score

def grouper(results,name):
    while True: #重点1
        results[name] = yield from averager() 

def main(data):
    results = {}
    for name,scores in data.items():
        group = grouper(results,name)
        next(group)
        for score in scores:
            group.send(score)
        group.send(None)#重点2
    
    print(results)
        
if __name__ == '__main__':
    main(data)

可以看出,凡是使用yield from结构,总共至少应该包含三部分:

1,委派生成器。即包含yield from表达式的部分,如上面的grouper方法;

2,子生成器。负责具体生成数据,如上面的averager方法;

3,调用方。负责调用委派生成器,如上面的main(data)方法。

这个程序除了前面讲到4点及3部分以外,还要强调上面代码后面注释的两点:

重点2:为什么要发送一个None?

将None传入grouper,是为了终止averager实例,并将averager返回值赋给results[name]。如果不传这个None,委派生成器grouper会一直停留在yield from averager()处,不会给results[name]赋值。

重点1:这里为什么要加while True?

如果我们不加while True,那么当group.send(None)发送给averager()后,通过break中止程序averager(),程序会给results[name]赋值,然后因为后面没有yield了,所以grouper()会抛出StopIteration异常。其实也可以不加while True,而像下面这样加一个yield即可:

def grouper(results,name):
    results[name] = yield from averager() 
    yield

我们上面说过,yield from可以捕获异常,但是grouper()本身也是一个生成器,它的StopIteration异常并没有处理。这个yield的作用就是让grouper不终止,从而不抛出StopIteration异常。当然也可以这样:

def main(data):
    results = {}
    for name,scores in data.items():
        group = grouper(results,name)
        next(group)
        for score in scores:
            group.send(score)
        try:
            group.send(None)
        except StopIteration:
            pass  
    print(results)

也就是在main方法中,手动捕获异常,防止程序出错。

 

 

你可能感兴趣的:(python)