A coroutine is a process that cooperates with a caller to produce a value provided by the caller.
1.1. Basic behavior of generators used as coroutines
>>> 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)
'GEN_CREATED' # inspect.getgeneratorstate 函数指明 GEN_CREATED 状态.
>>> next(my_coro2) # 激活协程,协程向前执行到第一个 yield 表达式,
-> Started: a = 14 # 打印
14 # 产出 a 的值
>>> getgeneratorstate(my_coro2)
'GEN_SUSPENDED' # GEN_SUSPENDED 状态
>>> my_coro2.send(28) # 将数字 28 发送给暂停的协程;计算 yield 表达式,得到 28,
-> Received: b = 28 # 并将其绑定给 b. 打印
42 # 产出 a + b,然后暂停,等待为 c 赋值.
>>> my_coro2.send(99) # 将数字 99 发送给暂停的协程;计算 yield 表达式,得到 99,
-> Received: c = 99 # 并将其绑定给 c. 打印
Traceback (most recent call last): # 协程终止, 生成器对象抛出 StopIteration 异常.
File "<stdin>", line 1, in <module>
StopIteration
>>> getgeneratorstate(my_coro2) # 指明协程处于 GEN_CLOSED 状态.
'GEN_CLOSED'
The point is that the coroutine yield
pauses execution at the point of the keyword: in an assignment statement, =
the code on the right is executed before the assignment. So, for b = yield a
this line of code, the value that won't be set until the client code activates the coroutine again b
. yield
This is what happens in asynchronous programming
1.2. Example of coroutine use: calculation of moving average
1.2.1 Class method implementation
>>> class Averager():
...
... def __init__(self):
... self.series = []
...
... def __call__(self, new_value):
... self.series.append(new_value)
... total = sum(self.series)
... return total/len(self.series)
...
>>> avg = Averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
1.2.2 Implementation of higher-order function closures
Implementation one:
>>> def make_averager():
... series = [] # series 是 make_averager 函数的局部变量
...
... def averager(new_value):
... series.append(new_value) # series 是 自由变量(未在本地作用域中绑定的变量)
... total = sum(series)
... return total/len(series)
...
... return averager
...
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
>>> avg.__code__.co_varnames # avg 的局部变量
('new_value', 'total')
>>> avg.__code__.co_freevars # avg 的自由变量
('series',)
>>> avg.__closure__ # series 的绑定在返回的 avg 函数的 __closure__ 属性中。
# avg.__closure__ 中的各个元素对应于 avg.__code__.co_freevars
# 中的一个名称。
(<cell at 0x7f06f930ac18: list object at 0x7f06f89d1e08>,)
>>> avg.__closure__[0].cell_contents # 这些元素是 cell 对象,有个 cell_contents 属性,保存这真正的值。
[10, 11, 12]
Implementation two:
>>> def make_averager():
... total = 0
... count = 0
...
... def averager(new_value):
... nonlocal count, total # nonlocal 声明,把变量标记为自由变量。
... count += 1 # 为nonlocal声明的变量赋予新值,闭包中保存的绑定会更新。
... total += new_value
... return total/count
...
... return averager
...
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
>>> avg.__code__.co_varnames
('new_value',)
>>> avg.__code__.co_freevars
('count', 'total')
>>> avg.__closure__
(<cell at 0x7f06f930ac18: int object at 0xa69e40>, <cell at 0x7f06f897b348: int object at 0xa6a200>)
>>> avg.__closure__[0].cell_contents
3
>>> avg.__closure__[1].cell_contents
33
>>>
averager()
nonlocal
A declaration is used in a function . Because when assigning a value to a free variable, a local variable is created implicitly, eg: count = count + 1
, a local variable is created implicitly count
. That way, count
it's not a free variable, and it's not stored in a closure. Using nonlocal
declared variables avoids the above problems.
1.2.3 Coroutine method implementation
Implementation 1: no return value
>>> def averager():
... total = 0.0
... count = 0
... average = None
...
... while True:
... term = yield average
... total += term
... count += 1
... average = total/count
...
>>> coro_avg = averager()
>>> from inspect import getgeneratorstate
>>> getgeneratorstate(coro_avg)
'GEN_CREATED'
>>> next(coro_avg)
>>> coro_avg.send(10)
10.0
>>> coro_avg.send(11)
10.5
>>> coro_avg.close()
>>> getgeneratorstate(coro_avg)
'GEN_CLOSED'
Implementation 2: There is a return value
from collections import namedtuple
from functools import wraps
Result = namedtuple('Result', 'count average')
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
if term is None:
break
total += term
count += 1
average = total/count
return Result(count, average)
"""
>>> coro_avg = averager()
>>> from inspect import getgeneratorstate
>>> getgeneratorstate(coro_avg)
'GEN_SUSPENDED'
>>> coro_avg.send(10)
10.0
>>> coro_avg.send(20)
15.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(None) # 发送 None 终止循环,导致协程结束,返回结果
Traceback (most recent call last): # 生成器对象会抛出 StopIteration 异常。
File "<stdin>", line 1, in <module> # 异常对象的 value 属性保存着返回值。
StopIteration: Result(count=3, average=20.0) #
"""
"""
>>> coro_avg = averager()
>>> getgeneratorstate(coro_avg)
'GEN_SUSPENDED'
>>> coro_avg.send(10)
10.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(6.5)
15.5
>>> try:
... coro_avg.send(None)
... except StopIteration as exc:
... result = exc.value # 通过捕获 StopIteration 异常,获取 averager 返回值
...
>>> result
Result(count=3, average=15.5)
"""
Getting the return value of the coroutine is a circle. yield from
The struct automatically catches StopIteration
exceptions internally and also turns the value of the value
property into yield from
the value of the expression.
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)
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],
}
# 委派生成器
def grouper(results, key):
while True:
results[key] = yield from averager() # grouper 在该处暂停,等待 averager 实例处理客户端发来的值。
# averger 运行完毕后(客户端发送None),返回的值绑定到
# results[key] 上
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
))
# 调用方
def main(data):
results = {}
for key, values in data.items():
# group是调用grouper函数得到的生成器对象。group作为协程使用
group = grouper(results, key)
next(group)
for value in values:
# 把各个value传给grouper。传入的值最终到达averager函数中term = yield那一行
group.send(value) # 将 value 传送给 grouper。 传入的值最终到达 averager 函数
# term = yield 行,grouper 永远不知道传入的值是什么
# print(results)
group.send(None) # 必须发送 None ,否则 averager 子生成器永远不会终止,
# 委派生成器 group 永远不会再次激活,
# 因此永远不会为 results[key] 赋值。当外层 for 循环
# 重新迭代时会新建一个 grouper 实例并绑定到 group 变量上。
# 前一个 grouper 实例以及它创建的尚未终止的 averager 子
# 生成器实例被垃圾回收程序回收。
# print(results)
report(results)
if __name__ == '__main__':
main(data)