从生成器到协程

python的协程是从yieldyield from发展而来的,因为yield一开始是用来实现生成器的,为了使得协程不是某种生成器,在python 3.5开始提出使用新关键字asyncawait来实现了原生协程。

基于生成器的协程

使用yield实现的协程是基于生成器的,将在3.10弃用。建议没接触过python协程的直接接触async声明的原生协程

一个简单的协程例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def cor(init):
    index = init
    while True:
        recv = yield f'now at index {index}'
        print(f'recv: {recv}')
        index += 1


c = cor(init=0)
c.send(None)

r1 = c.send('1')
print(r1)

r2 = c.send('2')
print(r2)
  1. send()是实现协程的一个核心点
  2. 协程的参数用于协程开始之前进行相关初始化操作。
  3. 协程中表达式recv = yield result, 其中result是协程的返回值(没有result时表示没有返回值),而recv是用来接收外部传入到协程中的数据 一个注意的点是,程序的运行顺序是先接收数据,再返回值,即index+=1后再返回给r1

使用装饰器预激活协程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from functools import wraps


def decorator(coroutine):
    @wraps(coroutine)
    def func(*args, **kwargs):
        primed_cor = coroutine(*args, **kwargs)
        primed_cor.send(None)
        return primed_cor
    return func


@decorator
def coroutine():
    print('start...')
    while True:
        recv = yield
        print(f'recv: {recv}')


cor = coroutine()
cor.send(1)
cor.send(2)
cor.send(3)
cor.close()

yield from

yield from的实际含义是委托给(delegate to)。

官网文档上一个简单的例子:

1
2
3
4
5
6
7
def g(x):
    yield from range(x, 0, -1)
    yield from range(x+1)


print(list(g(5)))
>>> [5, 4, 3, 2, 1, 0, 1, 2, 3, 4]

这个例子是偏向生成器的,而我们使用协程关注的是双方通信的。见下面的例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def c1(flag=False):
    if flag:
        return 'c1'
    else:
        yield

def c2(flag=False):
    if flag:
        return 'c2'
    else:
        yield


def cor():
    r1 = yield from c1(flag=True)
    r2 = yield from c2(flag=True)
    print(r1, r2)

c = cor()
c.send(None)

输出:

1
2
3
4
5
c1 c2
Traceback (most recent call last):
  File "test.py", line 20, in <module>
    c.send(None)
StopIteration

可以发现yield from实现通信的方式十分令人迷惑,r1 = yield from c1()我们想从c1()中拿到返回值,是在c1中return,而不是在c1中yield。而这与之前使用recv = yield result的这种写法不一致。

参考另一个官网文档上的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def accumulate():
    tally = 0
    while True:
        nxt = yield
        if nxt is None:
            return tally
        tally += nxt


def gather_tallies(tallies):
    while True:
        tally = yield from accumulate()
        tallies.append(tally)

if __name__ == '__main__':
    tallies = []
    acc = gather_tallies(tallies)
    next(acc)
    for i in range(4):
        acc.send(i)

    acc.send(None)             # accumulate()第一次return
    for i in range(5):
        acc.send(i)
    acc.send(None)             # accumulate()第二次return

    print(tallies)

输出:

1
[6, 10]

我们同样关注yield from是如何返回值的, 这个例子是通过send(None)而实现一次返回(return),仍旧是类似的判断返回

yield from写一段令人迷惑的程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def cal_1():
    recv = yield
    print('print at cal_1:', recv)
    yield from cal_2()

def cal_2():
    recv = yield
    print('print at cal_2:', recv)

def cor():
    print('start')
    while True:
        yield from cal_1()

c = cor()
c.send(None)
c.send(2)
c.send(3)
c.send('stop')

猜猜上面这段代码的输出是什么?输出如下:

1
2
3
4
start
print at cal_1: 2
print at cal_2: 3
print at cal_1: stop

另外,这段代码执行完毕就退出了,而不是陷在while循环中