协程(yield)

协程

用作协程的生成器

def simple_coroutine():
    print('-> coroutine started')
    x = yield # 此处返回None
    print('-> coroutine received:', x)
    
my_coro = simple_coroutine()
my_coro 
next(my_coro) # 预激活,这里执行到x = yield |的后面,即返回了None
my_coro.send(42) # 这里执行yield计算42,赋值给x,即执行x = yield
# 获取协程状态

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)
from inspect import getgeneratorstate
getgeneratorstate(my_coro2)
next(my_coro2) # 预激,执行到 b = yield之前,即输出了‘a’的值,等待为b赋值
getgeneratorstate(my_coro2)
my_coro2.send(28) # 赋值b = 28, 执行到c = yield之前,输出a + b, 并等待为c赋值
getgeneratorstate(my_coro2)
my_coro2.send(99) # 协程终止
getgeneratorstate(my_coro2)

使用协程计算移动平均值

def averager():
    total = 0.0 
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total / count
        
coro_avg = averager()
next(coro_avg) # 预激
coro_avg.send(10)
coro_avg.send(5)
coro_avg.close() # 一直接收值,直到关闭或者没有对协程的引用而被垃圾回收程序回收时,才会终止

预激协程的装饰器

from functools import wraps

def coroutine(func):
    
    @wraps(func)
    def primer(*args, **kwargs):
        gen = func(*args, **kwargs)
        next(gen)
        return gen
    # 替换生成器函数
    return primer 

@coroutine
def averager():
    total = 0.0 
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total / count

终止协程和异常处理

  • 未处理的异常会导致协程终止
coro_avg = averager()
next(coro_avg) # 预激
coro_avg.send(10)
coro_avg.send(5)
coro_avg.send('spam') # 抛出TypeError异常
coro_avg.send(1) # 协程已经终止了
getgeneratorstate(coro_avg)
  • 处理协程异常
class DemoException(Exception):
    """异常"""
    
def demo_exc_handling():
    print('-> coroutine started')
    try:
        while True:
            try:
                x = yield
            except DemoException:
                print('*** DemoException handled. Continuing...')
            else:
                print('-> coroutine received: {!r}'.format(x))
    finally:
        print('-> coroutine ending')
   
exc_coro = demo_exc_handling()
next(exc_coro)
exc_coro.send(11)
exc_coro.throw(DemoException) # 传入异常
getgeneratorstate(exc_coro) # 没有终止
exc_coro.send('spam')
getgeneratorstate(exc_coro) # 没有终止

exc_coro.throw(ZeroDivisionError) # 没有处理的异常,将导致协程终止

让协程返回值

from collections import namedtuple

Result = namedtuple('Result', 'count average')

def averager():
    total = 0.0 
    count = 0
    average = None 
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total / count 
    return Result(count, average)

coro_avg = averager()
next(coro_avg)
coro_avg.send(10)
coro_avg.send(5)
try:
    coro_avg.send(None) # 结束协程
except StopIteration as exc:
    result = exc.value # averager返回的值在异常的value中
    
result
    

使用yield from

  • 简化for循环的yield表达式
def gen():
    for c in 'AB':
        yield c
    for i in range(1, 3):
        yield i
        
list(gen())

def gen():
    yield from 'AB'
    yield from range(1, 3)
    
list(gen())
  • 通过yield from打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样而这可以直接发送和产出值
from collections import namedtuple

Result = namedtuple('Result', 'count average')

# 子生成器
def averager():
    total = 0.0 
    count = 0
    average = None 
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total / count 
    return Result(count, average)

# 委派生成器
def grouper(results, key):
    while True:
        results[key] = yield from averager()

# 调用方
def main(data):
    results = {}
    for key, values in data.items():
        group = grouper(results, key)
        next(group) # 预激group协程
        for value in values:
            # grouper永远不知道传入的值
            group.send(value)
        group.send(None)
    print(results)
    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.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
 'girls;m': [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
 'boys;kg': [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
 'boys;m': [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46]}

main(data)

总结

  • 用作协程的生成器的行为,协程在yield表达式处暂停,等待send()或throw()
  • 协程需要next()预激,否则无法使用
  • 协程的异常处理,没有处理的异常将导致协程终止
  • 让协程返回值,协程的返回值包含在异常对象的value属性中
  • yield from打开双向通道,调用方和子生成器可以通过双向通道直接传值

流畅的Python

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值