美文网首页
python 生成器和协程

python 生成器和协程

作者: 尛白兔 | 来源:发表于2017-09-16 16:43 被阅读14次

yield

对于python生成器中的yield来说,yield item会产出一个值,提供给next()的调用方,然后挂起生成器,直到再调用next()。

def func():
    for i in range(10):
        yield i

f = func()
next(f) 

协程

在协程中,yield通常出现在表达式的右边(data = yield),可以返回一个值,也可以不返回(如果yield后面没有表达式,则返回None)。 生成器的调用方可以使用send()方法发送数据,发送的数据会成为yield表达式的值。因此,生成器可以作为协程使用。

协程是一个过程,这个过程与调用方协作,产出由调用方提供的值

>>> def func():
...     for i in range(10):
...             r = yield i    #遇到yield会暂时挂起生成器,并返回i
...             print(r)
... 
>>> f = func()
>>> next(f)     # 启动生成器才能向生成器发送数据
0
>>> f.send(10)
10
1

*** 在使用f.send()之前,一定要先预激协程 ***

为了简化协程的用法,可以使用一个预激装饰器

import functools
def wrapper(func):
    @functools.wraps(func)    # 使用wraps是为了保留func的函数名
    def inner(*args, **kwargs):
        gen = func(*args, **kwargs)
        next(gen)
        return gen
    return inner


def func():
    for i in range(10):
        r = yield i
        print(r)

终止协程和异常处理

如果协程内没有处理异常, 协程会终止,再次调用协程,会抛出StopIteration异常

>>> f = func()
>>> f.send(10)
100
1
>>> f.send('hello')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in func
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
>>> f.send(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

让协程返回值

在协程中使用return返回的值会放在StopIteration异常中的value变量中,捕获这个异常,然后获取即可

yield from

使用yield from 会自动捕获StopIteration异常,还会把value变量的值变成yield from 表达式的值。

def func():
    for i in range(10):
        r = yield i
    
yield from rang(10) 等同于func()

yield from 将调用方法与内层的子生成器连接起来, 外层的调用方法可以向内层的生成器直接传递值,子生成器返回的值由yield from接收.
下面看一个例子

#!/usr/bin/env python3
from collections import namedtuple

Result = namedtuple('Result', 'count average')# 子生成器

# 这个例子和上边示例中的 averager 协程一样,只不过这里是作为字生成器使用

def averager():
    total = 0.0
    count = 0
    average = None
    while True:        # main 函数发送数据到这里 
        term = yield
        if term is None: # 终止条件
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average) # 返回的Result 会成为grouper函数中yield from表达式的值


# 委派生成器
def grouper(results, key):
    # 这个循环每次都会新建一个averager 实例,每个实例都是作为协程使用的生成器对象
    while True:        # grouper 发送的每个值都会经由yield from 处理,通过管道传给averager 实例。grouper会在yield from表达式处暂停,等待averager实例处理客户端发来的值。averager实例运行完毕后,返回的值绑定到results[key] 上。while 循环会不断创建averager实例,处理更多的值。
        results[key] = yield from averager()
    
# 调用方
def main(data):
    results = {}
    for key, values in data.items():
        # group 是调用grouper函数得到的生成器对象,传给grouper 函数的第一个参数是results,用于收集结果;第二个是某个键
        group = grouper(results, key)
        next(group)        
        for value in values:            # 把各个value传给grouper 传入的值最终到达averager函数中;
            # grouper并不知道传入的是什么,同时grouper实例在yield from处暂停
            group.send(value)        # 把None传入groupper,传入的值最终到达averager函数中,导致当前实例终止。然后继续创建下一个实例。
        # 如果没有group.send(None),那么averager子生成器永远不会终止,委派生成器也永远不会在此激活,也就不会为result[key]赋值
        group.send(None)
    report(results)
        
# 输出报告
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit))

data = {
    'girls;kg':[40, 41, 42, 43, 44, 54],
    'girls;m': [1.5, 1.6, 1.8, 1.5, 1.45, 1.6],    
    'boys;kg':[50, 51, 62, 53, 54, 54],    
    'boys;m': [1.6, 1.8, 1.8, 1.7, 1.55, 1.6],
}

if __name__ == '__main__':
    main(data)

相关文章

网友评论

      本文标题:python 生成器和协程

      本文链接:https://www.haomeiwen.com/subject/iifusxtx.html