Python Coroutine

Reason is the light and the light of life.

Jerry Su Apr 02, 2021 1 mins

yield: 产出和让步

在协程中yield通常出现在等号的右边 data = yield,可以产出值,也可以不产出。

data = yield # yield关键字后没有表达式,表示没有产出值,即None

协程从调用方通过.send(data)接收数据。

yield还是一种流程控制工具,把cpu让步给调度器,从而激活其他协程。

生成器如何进化成协程

生成器的调用方可以使用.send(…)方法发送数据,发送的数据会成为生成器函数中yield表达式的值。因此,生成器可以作为协程使用。协程是指一个过程,这个过程预调用方协作,产出由调用方提供的值。

def simple_coroutine1():    # 协程使用生成器函数定义:定义体内有yield关键字
    print("coroutine started")
    x = yield  # 如果协程只需要从客户那里接收数据,那么产出的值是None,因此yield右边没有表达式。
    print(f"coroutine received: {x}")
my_cor1 = simple_coroutine1()     # 创建协程对象,并非函数调用
my_cor1
<generator object simple_coroutine1 at 0x7fbe19f3f890>
next(my_cor1)  # 启动协程,预激协程
coroutine started
my_cor1.send(42) # 调用send方法,协程定义体内的yield表达式会计算出42,协程恢复运行到下一个yield表达式。
coroutine received: 42



---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-10-29934ae2c704> in <module>
----> 1 my_cor1.send(42)


StopIteration:
# 重点:协程在关键字yield所在的位置暂停执行。在赋值语句中, =右边的代码在赋值之前执行。
# 因此 b = yield a,等到客户端再激活协程时才会给b赋值。
def simple_coroutine2(a):
    print(f"Started: a = {a}")
    b = yield a
    print(f"Received: b = {b}")
    c = yield a + b
    print(f"Received: c = {c}")
my_cor2 = simple_coroutine2(12)
next(my_cor2)
Started: a = 12





12
my_cor2.send(5)
Received: b = 5





17
my_cor2.send(1)
Received: c = 1



---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-15-6d7672aae6d6> in <module>
----> 1 my_cor2.send(1)


StopIteration:

计算移动均值

def averager():
    sum, cnt, average = 0., 0, None
    while True:
        term = yield average
        sum += term
        cnt += 1
        average = sum / cnt
avg = averager()   
next(avg)  
avg.send(10)
10.0
avg.send(30)
20.0
avg.send(5)
15.0

如何启动协程?

预激协程的装饰器

def coroutine(func):
    def primer(*args, **kwargs):
        gen = func(*args, **kwargs) # 获取生成器对象
        next(gen)                   # 预激生成器
        return gen                  # 返回生成器
    return primer
@coroutine
def averager():
    sum, cnt, average = 0., 0, None
    while True:
        term = yield average
        sum += term
        cnt += 1
        average = sum / cnt
from inspect import getgeneratorstate
cor_avg = averager()
getgeneratorstate(cor_avg)
'GEN_SUSPENDED'
cor_avg.send(10)
10.0
getgeneratorstate(cor_avg)
'GEN_SUSPENDED'


Read more:

Related posts: