python中的生成器主要经历三个发展阶段:
- 作为实现迭代器协议的一种方式而出现(yield语句)
- 增添方法,使生成器能够支持协程(增加close,send,throw方法,调整yield语句为yield表达式)
- 为了重构方便增加了yield from 语法
在这篇博文中,主要介绍用于第二点
关于协程
协程,coroutine, 这个名称相对于当代操作系统多任务的抢占式协作(随着时间片的耗尽,进程/线程将会被操作系统剥夺cpu的使用权,然后分配给其他进程/线程),协程是主动交出cpu的使用权。
在python的生成器概念提出来时,生成器的概念已经很接近协程的概念了,试想一下,一个yield语句将会暂停(挂起)当前函数的执行(主动让出cpu的使用权),将一个中间值返回给函数的调用者;而为了恢复(resume)函数的执行,调用者可以使用next(gen)使生成器在挂起的点继续执行。但是还是有以下几点区别:
- 在恢复函数执行的时候,无法向生成器传递参数或者向生成器抛出一个异常
- 无法在try/finally的try分句中使用yield, 导致生成器在弃用之后无法进行资源释放
丰富生成器的行为
为了使生成器更加接近于协程的概念,python在pep342中做出了以下的改变:
将yield从原来的语句(statement)调整为表达式(expression)
为生成器对象增加了以下几个方法:
send方法: send方法的参数作为yield表达式的值;
1
2
3
4
5
6
7
8
9def generator_function():
value = yield "yield from func"
print "receive outside the gen_obj:", value
gen_obj = generator_function()
print gen_obj.send(None)
## yield from func
gen_obj.send(42) ## 42 将会作为 yield "yield from func" 表达式的值赋值给value
## receive outside the gen_obj: 42
throw方法:throw方法的参数是一个异常,该异常将会在生成器对象的yield表达式的位置被抛出;
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
27def generator_function():
try:
value = yield "yield from func"
except ValueError:
print "valueerror"
gen_obj = generator_function()
gen_obj.send(None)
'yield from func'
gen_obj.throw(ValueError)
valueerror ## 在gen_obj 中被捕捉,执行except的代码块
Traceback (most recent call last): ## 然后向调用者抛出一个StopIteration异常,
File "<pyshell#68>", line 1, in <module> ## 表示生成器已经结束
gen_obj.throw(ValueError)
StopIteration
gen_obj1 = generator_function()
gen_obj1.send(None)
'yield from func'
gen_obj1.throw(AttributeError) ## 向gen_obj1抛出异常,由于AttributeError异常没有在
## 生成器内部被捕捉, 该AttributeError异常将会被向调 ## 用者抛出
Traceback (most recent call last):
File "<pyshell#74>", line 1, in <module>
gen_obj1.throw(AttributeError)
File "<pyshell#65>", line 3, in generator_function
value = yield "yield from func"
AttributeError
close方法: close方法主要为生成器提供外部资源释放的能力,该方法会在生成器被gc回收的时候调用。close方法相当于gen_obj.throw(GeneratorExit), 将向生成器对象抛出一个GeneratorExit异常表示生成器推出了,主要用于确保在try/finally结构的try分句包含yield表达式时,finally总能够被执行(显示调用close方法,或者在gc回收时,隐式调用close方法)
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
28
29
30
31
32
33
34
35
36
37
38
39def gen_func():
yield "Hello"
try:
value = yield 42
print '42'
finally:
print "finally"
gen_obj1 = gen_func()
gen_obj1.send(None)
'Hello'
gen_obj1.throw(ValueErro)
Traceback (most recent call last):
File "<pyshell#82>", line 1, in <module>
gen_obj1.throw(ValueErro)
NameError: name 'ValueErro' is not defined
gen_obj2 = gen_func()
gen_obj2.send(None)
'Hello'
gen_obj2.close()
gen_obj1 = gen_func()
gen_obj1.send(None)
'Hello'
gen_obj1.close()
gen_obj2 = gen_func()
gen_obj2.send(None)
'Hello'
gen_obj2.send(None)
42
gen_obj2.close()
finally
gen_obj3 = gen_func()
gen_obj3.send(None)
'Hello'
gen_obj3.send(None)
42
del gen_obj3
finally