文章目录
- 一个协程的简单演示
- 产出两个值的协程
- 使用协程计算移动平均值
- 预激协程的装饰器
- 终止协程和异常处理
- `yield from`的使用
- 委派生成器和子生成器
- `yield from` 结构的另外两个特性
一个协程的简单演示
from inspect import getgeneratorstate
def simple_coroutine():
print('-> coroutine started')
x = yield
print('-> coroutine received:', x)
my_coro = simple_coroutine()
print(my_coro) # <generator object simple_coroutine at 0x000002A539D8D830>
next(my_coro) # -> coroutine started ,首先要先调用next()函数激活协程
my_coro.send(42) # -> coroutine received: 42 ,StopIteration
产出两个值的协程
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)
print(getgeneratorstate(my_coro2)) # GEN_CREATED ,协程未启动状态
print(next(my_coro2)) # -> Started: a = 14 14,向前执行协程到第一个yield表达式,打印出此消息,然后产出a的值并暂停,等待为b赋值
print(getgeneratorstate(my_coro2)) # GEN_SUSPENDED ,协程在yield表达式暂停 状态
print(my_coro2.send(28)) # -> Received: b = 28 42 ,将28发给暂停的协程,计算yield表达式,得到28,然后将其绑定给b。打印此消息,产出a + b的值(42),然后暂停协程,等待为c赋值
my_coro2.send(99) # -> Received: c = 99 ,StopIteration
print(getgeneratorstate(my_coro2)) # GEN_CLOSED ,协程执行结束状态
使用协程计算移动平均值
total
、count
声明为局部变量即可,无需使用实例属性或闭包在多次调用之间保持上下文
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield average # yield表达式用于暂停执行协程,把结果发给调用方;还用于接收调用方后米娜发给协程的值,恢复无限循环
total += term
count += 1
average = total/count
coro_avg = averager()
# coro_avg.send(None) # 向协程发送None也可以激活协程,效果和下列代码一样
next(coro_avg) # 调用next()激活协程,协程向前执行到yield表达式,产出变量值的初始值--None
print(coro_avg.send(10)) # 10.0
print(coro_avg.send(30)) # 20.0
print(coro_avg.send(5)) # 15.0
预激协程的装饰器
为了简化协程的用法,会使用一个预激装饰器(使用yield from
句法调用协程时,会自动预激,与下面的@coroutine
之类的装饰器不兼容)
from functools import wraps
def coroutine(func):
"""装饰器:向前执行到第一个'yield'表达式,预激'func'"""
@wraps(func) # wraps装饰器保证原函数的属性不变
def primer(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen)
return gen
return primer
@coroutine
def average():
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total/count
coro_avg = average() # 在coroutine装饰器的primer函数中已经预激了这个生成器
print(getgeneratorstate(coro_avg)) # GEN_SUSPENDED ,表明该协程已经准备好了,可以接收值了
print(coro_avg.send(10)) # 10.0
print(coro_avg.send(30)) # 20.0
print(coro_avg.send(5)) # 15.0
终止协程和异常处理
未处理的异常会导致协程终止:
print(coro_avg.send('spam')) # TypeError: unsupported operand type(s) for +=: 'float' and 'str'
try:
coro_avg.send('spam')
except TypeError:
pass
print(coro_avg.send(5)) # StopIteration ,试图重新激活协程,抛出此异常
在协程中处理异常:
class DemoException(Exception):
"""为这次演示定义的异常类型"""
def demo_exc_handling():
print('-> coroutine started')
while True:
try:
x = yield
except DemoException:
print('*** DemoException handled. Continuing...')
else:
print('-> coroutine received:{!r}'.format(x))
raise RuntimeError('This line should never run.') # 这一行不会被执行到
正常激活和关闭demo_exc_handling协程:
exc_coro = demo_exc_handling()
next(exc_coro) # -> coroutine started
exc_coro.send(11) # -> coroutine received:11
exc_coro.send(22) # -> coroutine received:22
exc_coro.close()
print(getgeneratorstate(exc_coro)) # GEN_CLOSED
将DemoException异常传入demo_exc_handling协程,它会处理后继续运行:
exc_coro = demo_exc_handling()
next(exc_coro) # -> coroutine started
exc_coro.send(11) # -> coroutine received:11
exc_coro.throw(DemoException) # *** DemoException handled. Continuing...
print(getgeneratorstate(exc_coro)) # GEN_SUSPENDED
传入协程的异常如果没有处理,协程会停止:
exc_coro = demo_exc_handling()
next(exc_coro) # -> coroutine started
exc_coro.send(11) # -> coroutine received:11
# exc_coro.throw(ZeroDivisionError) # ZeroDivisionError
try:
exc_coro.throw(ZeroDivisionError)
except ZeroDivisionError:
pass
print(getgeneratorstate(exc_coro)) # GEN_CLOSED
使用try/finally块在协程终止时执行操作:
def demo_finally():
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')
让协程返回值:
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: # 发送None会终止循环,导致协程结束,返回结果。一如既往,生成器对象会抛出StopIteration异常。异常对象的value属性保存着返回的值。
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(30)
coro_avg.send(6.5)
# coro_avg.send(None) # StopIteration: Result(count=3, average=15.5)
try:
coro_avg.send(None)
except StopIteration as exc: # 捕获异常,获取averager返回的值
result = exc.value
print(result) # Result(count=3, average=15.5)
yield from
的使用
简化for循环中的yield表达式:
def gen():
for c in 'AB':
yield c
for i in range(1, 3):
yield i
print(list(gen())) # ['A', 'B', 1, 2]
# 使用yield from改写
def gen():
yield from 'AB'
yield from range(1, 3)
print(list(gen())) # ['A', 'B', 1, 2]
使用yield from链接可迭代的对象:
def chain(*iterables):
for it in iterables:
yield from it
s = 'ABC'
t = tuple(range(3))
print(list(chain(s, t))) # ['A', 'B', 'C', 0, 1, 2]
yield from x
表达式对x
对象做的第一件事就是调用iter(x)
,从中获取迭代器,因此x
可以是任何可迭代的对象
委派生成器和子生成器
一个使用委派生成器和子生成器的示例:使用 yield from
计算平均值并输出统计报告
from collections import namedtuple
Result = namedtuple('Result', 'count average')
子生成器:
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield # main函数中客户代码发送的各个值绑定到这里的term变量上
if term is None: # 终止条件,确实会导致 使用yield from调用这个协程的生成器会永远阻塞
break
total += term
count += 1
average = total/count
return Result(count, average) # 返回的Result会成为grouper函数中yield from表达式的值
委派生成器:
def grouper(results, key):
while True: # 每次迭代时会新建一个averager实例;每个实例都是作为协程使用的生成器对象
results[key] = yield from averager()
客户端代码,即调用方:
def main(data):
results = {}
for key, values in data.items():
group = grouper(results, key)
next(group)
# group.send(None) ,这行代码效果等效上面的代码,都是预激协程
for value in values:
group.send(value)
group.send(None)
print(results) # 用于调试
'''{'girls;kg': Result(count=10, average=42.040000000000006),
'girls;m': Result(count=10, average=1.4279999999999997),
'boys;kg': Result(count=9, average=40.422222222222224),
'boys;m': Result(count=9, average=1.3888888888888888)}'''
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],
}
if __name__ == '__main__':
main(data)
'''
9 boys averaging 40.42kg
9 boys averaging 1.39m
10 girls averaging 42.04kg
10 girls averaging 1.43m
'''
- 子生成器产出的值都直接传给委派生成器的调用方(即客户端代码);
- 使用
send()
方法发给委派生成器的值都直接传给子生成器。如果发送的值是None
,那么会调用子生成器的__next__()
方法。如果发送的值不是None
,那么会调用子生成器的send()
方法。如果调用的方法抛出StopIteration
异常,那么委派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器; - 生成器退出时,生成器(或子生成器)中的
return expr
表达式会触发StopIteration(expr)
异常抛出; - yield from 表达式的值是子生成器终止时传给
StopIteration
异常的第一个参数。
yield from
结构的另外两个特性
- 传入委派生成器的异常,除了
GeneratorExit
之外都传给子生成器的throw()
方法。如果调用throw()
方法时抛出StopIteration
异常,委派生成器恢复运行。StopIteration
之外的异常会向上冒泡,传给委派生成器。 - 如果把
GeneratorExit
异常传入委派生成器,或者在委派生成器上调用close()
方法,那么在子生成器上调用close()
方法,如果它有的话。如果调用close()
方法导致异常抛出,那么异常会向上冒泡,传给委派生成器;否则,委派生成器抛出GeneratorExit
异常。