首页 > 技术文章 > Coroutine 练习 1 - Coroutine Exercises 1

zzyzz 2017-11-14 17:17 原文

 1 Coroutine 练习 1 - Coroutine Exercises 1
 2 
 3 字典中为动词 “to yield” 给出了两个释义:产出和让步。对于 Python 生成器中的 yield 4 说,这两个含义都成立。 yield item 这行代码会产出一个值,提供给 next(...) 的调
 5 用方;此外,还会作出让步,暂停执行生成器,让调用方继续工作,直到需要使用另一个
 6 值时再调用 next()。调用方会从生成器中拉取值。
 7 从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数。可是,在协
 8 程中, yield 通常出现在表达式的右边(例如, datum = yield),可以产出值,也可
 9 以不产出,如果 yield 关键字后面没有表达式,那么生成器产出 None。协程可能会从
10 调用方接收数据,不过调用方把数据提供给协程使用的是 .send(datum) 方法,而不是
11 next(...) 函数。通常,调用方会把值推送给协程。
12 yield 关键字甚至还可以不接收或传出数据。不管数据如何流动, yield 都是一种流程控
13 制工具,使用它可以实现协作式多任务:协程可以把控制器让步给中心调度程序,从而激
14 活其他的协程。从根本上把 yield 视作控制流程的方式,这样就好理解协程了。
15 
16 生成器如何进化成协程
17     协程的底层架构在 “PEP 342—Coroutines via Enhanced Generators”
18     (https://www.python.org/dev/peps/pep-0342/)中定义,并在 Python 2.5(2006
19     年)实现了。自此之后, yield 关键字可以在表达式中使用,而且生成器 API 中增加了
20     .send(value) 方法。生成器的调用方可以使用 .send(...) 方法发送数据,发送的数据
21     会成为生成器函数中 yield 表达式的值。因此,生成器可以作为协程使用。协程是指一
22     个过程,这个过程与调用方协作,产出由调用方'提供'的值。
23     除了 .send(...) 方法, PEP 342 还添加了 .throw(...) 和 .close() 方法:前者的作
24     用是让调用方抛出异常,在生成器中处理;后者的作用是终止生成器。
25 
26     协程最近的演进来自 Python 3.3(2012 年)实现的
27     “PEP 380—Syntax for Delegating to a Subgenerator”
28     (https://www.python.org/dev/peps/pep-0380/)。
29     PEP 380 对生成器函数的句法做了两处改动,以便更好地作为协程使用。
30     现在,生成器可以返回一个值;以前,如果在生成器中给 return 语句提供值,会抛
31     出 SyntaxError 异常。新引入了 yield from 句法,使用它可以把复杂的生成器重构成小型的嵌套生成器,
32     省去了之前把生成器的工作委托给子生成器所需的大量样板代码。
33 
34 例子,
35     def coroutine_example():
36         print ('coroutine started')
37         x = yield                              #  协程使用生成器函数定义:定义体中有 yield 关键字。
38                                            #  yield 在表达式中使用;如果协程只需从客户那里接收数据,那么产出的值是 None,
39                                            # 这个值是隐式指定的,因为 yield 关键字右边没有表达式
40         print ('coroutine received:', x)
41 
42     Output,
43         >>> coro = coroutine_example()
44         >>> print (coro)                   # 与创建生成器的方式一样,调用函数得到生成器对象
45         <generator object coroutine_example at 0x034458D0>
46         >>> next(coro)  #  首先要调用 next(...) 函数,因为生成器还没启动,没在 yield 语句处暂停,所以一开始无法发送数据
47         coroutine started  # 携程已激活, 等待数据过来
48                            #  这一步通常称为“预激”(prime)协程(即,让协程向前执行到第一个 yield 表达式,准备好作为活跃的协程使用)
49         >>>coro.send('haha')  # 调用 send 方法后,协程定义体中的 yield 表达式会计算出 haha;
50                               # 现在,协程会恢复,一直运行到下一个 yield 表达式,或者终止。
51         coroutine received: haha
52         StopIteration         # 这里,控制权流动到协程定义体的末尾,导致生成器像往常一样抛出 StopIteration 异常。
53 
54 协程可以身处四个状态中的一个。
55 当前状态可以使用 inspect.getgeneratorstate(...) 函数确定,该函数会返回下述字符串中的一个。
56     'GEN_CREATED'
57        等待开始执行。
58     'GEN_RUNNING'
59        解释器正在执行。
60         只有在多线程应用中才能看到这个状态。此外,生成器对象在自己身上调用 getgeneratorstate 函数也行,不过这样做没什么用。
61     'GEN_SUSPENDED'
62       在 yield 表达式处暂停。
63     'GEN_CLOSED'
64       执行结束。
65 
66 因为 send 方法的参数会成为暂停的 yield 表达式的值,所以,仅当协程处于暂停状态时
67 才能调用 send 方法。不过,如果协程还没激活(即,状态是 'GEN_CREATED'),情况就不同了。
68 因此,始终要调用 next(my_coro) 激活协程——也可以调用 my_coro.send(None),效果一样。
69 
70 例子,
71     def coroutine_example2(a):
72         print ('coroutine start a = ',a)   #1
73         b = yield a
74         print ('received b = ', b)         #2
75         c = yield a + b
76         print ('received c = ', c)
77 
78         coro2= coroutine_example2(5)
79         print (next(coro2))                #3
80         #coro2.send(None)         # 调用 next(my_coro) 或 my_coro.send(None) 激活协程,效果一样.
81         print (coro2.send(7))              #4
82         coro2.send(10)
83 
84     Output,
85         coroutine start a =  5   # 向前执行协程到第一个 yield 表达式,打印 -> coroutine start a = 5(#1) 消息,
86                                  # 然后产出 a 的值,并且暂停,等待为 b 赋值.
87         5                        # 3
88         received b = 7           # 把 7 发给暂停的协程;计算 yield 表达式,得到 7,然后把那个数绑定给 b。
89                                  # 打印 -> Received: b = 7 (#2)消息,-> 程序继续,接着产出 a + b 的值(12),
90                                  # 然后协程暂停,等待为 c赋值。
91         12                       #
92 
93         received c =  10         # 把数字 10 发给暂停的协程;计算 yield 表达式,得到 10,然后把那个数绑定给 c,
94                                  # 进而, 打印 -> Received: c = 10 消息,然后协程终止,导致生成器对象抛出StopIteration 异常。
95         StopIteration

 

推荐阅读