协程
用作协程的生成器
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