【Python】笔记:协程
协程
用作协程的生成器的基本行为
- 协程使用生成器函数定义: 定义体中有
yield
关键字
def simple_coroutine():
print('-> coroutine start')
x = yield # 因为 yield 右边没有值, 所以产出 None
print('-> coroutine received: ', x)
coro = simple_coroutine()
print(1, repr(coro))
next(coro) # 协程运行到第一个 yield 处暂停, 等待发送数据
coro.send(233) # 协程恢复, 运行至下一个 yield 或者终止
1 <generator object simple_coroutine at 0x000001C7B11C8930>
-> coroutine start
-> coroutine received: 233
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Cell In [4], line 9
7 print(1, repr(coro))
8 next(coro)
----> 9 coro.send(233)
StopIteration:
协程有四种状态, 可以使用 inspect.getgeneratorstste(...)
来获取状态
状态 | 描述 |
---|---|
GEN_CREATED |
等待开始执行 |
GEN_RUNNING |
解释器正在执行(只有在多线程应用中才能看到这个状态, (或在生成器对象自己身上调用 getgeneratorstste , 不过没啥用)) |
GEN_SUSPENDED |
在 yield 处暂停 |
GEN_CLOSED |
执行结束 |
因为 send
方法的参数会成为 暂停的 yield
表达式的值, 所以, 仅当协程处于暂停状态时才能调用 send
方法
因此, 一开始要用 next(coro)
激活协程 (这一步称为 预激 (prime) 协程)
用 coro.send(None)
效果一样, 但如果发送除 None
以外的值, 会抛出 TypeError
coro = simple_coroutine()
coro.send(233)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In [5], line 2
1 coro = simple_coroutine()
----> 2 coro.send(233)
TypeError: can't send non-None value to a just-started generator
from inspect import getgeneratorstate
def simple_coroutine2(a):
print('-> Started: a =', a)
b = yield a
print('-> Received: b =', b)
c = yield a + b
print('-> Received: c =', c)
coro2 = simple_coroutine2(14)
print(1, getgeneratorstate(coro2))
print(2, next(coro2)) # 预激, 执行到第一个 yield, 并产出 a
print()
print(3, getgeneratorstate(coro2))
print(4, coro2.send(66)) # 执行到第二个 yield, 并产出 a + b
print()
print(5, coro2.send(233)) # 执行结束, 抛出异常
1 GEN_CREATED
-> Started: a = 14
2 14
3 GEN_SUSPENDED
-> Received: b = 66
4 80
-> Received: c = 233
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Cell In [9], line 18
16 print(4, coro2.send(66))
17 print()
---> 18 print(5, coro2.send(233))
StopIteration:
示例:使用协程计算移动平均值
def averager():
total = 0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total / count
aver = averager()
next(aver)
print(aver.send(233))
print(aver.send(234))
print(aver.send(235))
233.0
233.5
234.0
预激协程的装饰器
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
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total / count
coro3 = averager()
print(coro3.send(233))
print(coro3.send(234))
print(coro3.send(235))
233.0
233.5
234.0
终止协程和异常处理
通过 throw()
和 close()
显式将异常抛给协程, 让其处理/退出
-
generator.throw(exc_type[, exc_value[, traceback]])
致使生成器在暂停处抛出指定异常, 如果生成器处理了异常, 那么代码会向前执行到下一个yield
表达式, 而产生的值会成为调用generator.thow
方法得到的返回值 -
generator.close()
致使生成器在暂停处抛出GeneratorExit
异常, 如果生成器没有处理这个异常, 或抛出了GeneratorExit
异常, 调用方不会报错; 如果收到GeneratorExit
异常, 生成器一定不能产出值, 否则解释器会抛出RuntimeError
class DemoException(Exception):
'''一个新的异常'''
def demo_exc_handing():
print('-> coroutine started')
while True:
try:
x = yield
except DemoException:
print('-> DemoException handled...')
else:
print('-> coroutine received: {!r}'.format(x))
raise RuntimeError("This line should never run")
exc_coro = demo_exc_handing()
next(exc_coro)
exc_coro.send(233)
exc_coro.send(666)
exc_coro.throw(DemoException)
exc_coro.send(999)
exc_coro.close()
-> coroutine started
-> coroutine received: 233
-> coroutine received: 666
-> DemoException handled...
-> coroutine received: 999
exc_coro = demo_exc_handing()
next(exc_coro)
exc_coro.send(233)
exc_coro.throw(ZeroDivisionError)
-> coroutine started
-> coroutine received: 233
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
Cell In [14], line 4
2 next(exc_coro)
3 exc_coro.send(233)
----> 4 exc_coro.throw(ZeroDivisionError)
Cell In [12], line 9, in demo_exc_handing()
7 while True:
8 try:
----> 9 x = yield
10 except DemoException:
11 print('-> DemoException handled...')
ZeroDivisionError:
# 扫尾工作
class DemoException(Exception):
'''一个新的异常'''
def demo_exc_handing():
print('-> coroutine started')
try:
while True:
try:
x = yield
except DemoException:
print('-> DemoException handled...')
else:
print('-> coroutine received: {!r}'.format(x))
finally:
print('-> coroutine ending')
让协程返回值
from collections import namedtuple
Result = namedtuple('Result', 'count average')
def averager():
total = 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)
coro4 = averager()
next(coro4)
coro4.send(10)
coro4.send(30)
coro4.send(None) # 错误示范
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Cell In [17], line 5
3 coro4.send(10)
4 coro4.send(30)
----> 5 coro4.send(None) # 错误示范
StopIteration: Result(count=2, average=20.0)
此例中, 发送 None
会导致循环终止, 协程结束, 抛出 StopIteration
Result()
会保存在 StopIteration.value
coro5 = averager()
next(coro5)
coro5.send(10)
coro5.send(20)
try:
coro5.send(None)
except StopIteration as exc:
result = exc.value
print(result)
Result(count=2, average=15.0)
使用 yield from
yield from x
会调用 iter(x)
, 取得迭代器
# 复习一下 yield from 的基本用法
def gen():
for c in 'AB':
yield c
for i in range(1, 3):
yield i
print(list(gen()))
['A', 'B', 1, 2]
def gen():
yield from 'AB'
yield from range(1, 3)
print(list(gen()))
['A', 'B', 1, 2]
术语 | 说明 |
---|---|
委派生成器 | 包含 yield from <iterable> 表达式的生成器函数 |
子生成器 | 从 yield from 表达式中 <iterable> 部分获取的生成器 |
调用方/客户端 | 调用委派生成器的客户端代码 |
# 例:计算平均数们
from collections import namedtuple
Result = namedtuple('Result', 'count average')
# 子生成器
def averager():
total = 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)
for value in values:
group.send(value)
group.send(None) # 终止当前的 averager
# 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 = {
'human;kg': [65.78331, 71.51521, 69.39874, 68.2166, 67.78781, 68.69784, 69.80204, 70.01472, 67.90265, 66.78236, 66.48769, 67.62333, 68.30248, 67.11656, 68.27967, 71.0916, 66.461, 68.64927, 71.23033, 67.13118, 67.83379, 68.87881, 63.48115, 68.42187, 67.62804],
'human;cm': [112.9925, 136.4873, 153.0269, 142.3354, 144.2971, 123.3024, 141.4947, 136.4623, 112.3723, 120.6672, 127.4516, 114.143, 125.6107, 122.4618, 116.0866, 139.9975, 129.5023, 142.9733, 137.9025, 124.0449, 141.2807, 143.5392, 97.90191, 129.5027, 141.8501]
} # 注明一下, 数据来源于百度飞浆
main(data)
25 human averaging 130.31cm
25 human averaging 68.18kg
摘自 PEP 380
Any values that the iterator yields are passed directly to the caller.
Any values sent to the delegating generator using send() are passed directly to the iterator. If the sent value is None, the iterator’s
__next__()
method is called. If the sent value is not None, the iterator’s send() method is called. If the call raises StopIteration, the delegating generator is resumed. Any other exception is propagated to the delegating generator.
Exceptions other than
GeneratorExit
thrown into the delegating generator are passed to the throw() method of the iterator. If the call raisesStopIteration
, the delegating generator is resumed. Any other exception is propagated to the delegating generator.
If a
GeneratorExit
exception is thrown into the delegating generator, or theclose()
method of the delegating generator is called, then theclose()
method of the iterator is called if it has one. If this call results in an exception, it is propagated to the delegating generator. Otherwise, GeneratorExit is raised in the delegating generator.
The value of the
yield from
expression is the first argument to theStopIteration
exception raised by the iterator when it terminates.
return
expr in a generator causesStopIteration(expr)
to be raised upon exit from the generator.
本文来自博客园,作者:Zinc233,转载请注明原文链接:https://www.cnblogs.com/Zinc233/p/FluentPython_S16.html