从生成器到协程#
python的协程是从yield
和yield from
发展而来的,因为yield
一开始是用来实现生成器的,为了使得协程不是某种生成器,在python 3.5开始提出使用新关键字async
和await
来实现了原生协程。
基于生成器的协程#
使用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)
|
send()
是实现协程的一个核心点
- 协程的参数用于协程开始之前进行相关初始化操作。
- 协程中表达式
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)
|
输出:
我们同样关注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循环中