近期看了tornado的源码,gen模块使用yield实现了coroutine(协程),一个函数中一旦使用
了yield语句,那么这个函数可以说就是个generator function了。
example 1:
<span style="font-size:18px;"> 1 #coding: utf-8
2 def gen1():
3 print 'hi, this is in the gen1 generator'
4 yield 1
5 if __name__ == '__main__':
6 g = gen1()
7 print 'type(g)', type(g)</span></span>
在终端中执行结果如下:
type(g) <type 'generator'>
从输出可以看出,gen1不再是一个函数而是一个generator了,从输出可以看到,第六行
gen1()的调用,并没有真正的执行函数体,要是执行了的话,第三行应该可以输出内
容!这是generator function与普通函数的一个区别之一:普通函数在第一次调用就开始
执行函数体,而generator function的第一次调用只是生成了一个generator function
对象,并不执行函数体。
example 2:
<span style="font-size:18px;"> 1 #coding: utf-8
2 def gen2():
3 print 'hi, before yield 1'
4 yield 1
5 print 'hi, after yield 1'
6
7 if __name__ == '__main__':
8 g = gen2()
9 ret = g.next()
10 print 'ret :', ret</span></span>
在终端中输出如下:
hi, before yield 1
ret : 1
从输出可以看出,第5行明显没有执行,不然会有输出的。这是generator function的一个
特性:当一个generator function在执行到包含有yield关键字的语句时,函数的执行会
被挂起(例子中是gen2函数),从而回到调用方(if __name__ == '__main__'下面的语句),
yield后面的表达式计算的结果作为返回值返回给调用方(这里赋值给了ret变量)。如果再
次调用这个generator函数调用,函数会从上次被挂起的地方开始执行。在例子中应该是
第4行。
example 3:
<span style="font-size:18px;"> 1 #coding: utf-8
2 def gen3():
3 print 'hi, before yield 1'
4 yield 1
5 print 'hi, after yield 1'
6
7 if __name__ == '__main__':
8 g = gen3()
9 ret = g.next()
10 print 'ret :', ret
11 g.next()</span></span>
在终端中输出如下:
hi, before yield 1
ret : 1
hi, after yield 1
Traceback (most recent call last):
File "generator.py", line 11, in <module>
g.next()
StopIteration
输出中可以看出,第11行的调用输出了执行了gen3函数的第5行,然后抛出了一个异常。
这也说明了generator function的另一个特性:当整个generator function函数正常(在执
行期间没有抛出未捕获的异常)执行完毕时,会抛出一个StopIteration异常。这点和iterator
是一样的。
example 4:
<span style="font-size:18px;"> 1 #coding: utf-8
2 def gen4():
3 print 'hi, before yield 1'
4 yield 1
5 print 'hi, after yield 1'
6 return 2
7 if __name__ == '__main__':
8 g = gen4()</span></span>
在执行的时候输出如下:
File "generator.py", line 6
return 2
SyntaxError: 'return' with argument inside generator
也就是说有语法错误。这个异常也说明了generator function的一个特性:generator function
中return语句后面不能跟任何的表达式语句将代码中第六行的return 2 换成return就正确了。
example 5:
<span style="font-size:18px;"> 1 #coding: utf-8
2 def gen5():
3 n = yield 2
4 print 'n = ', n
5
6 def send_data(n):
7 g = gen5()
8 ret = g.next()
9 print 'ret = ', ret
10 g.send(n)
11 if __name__ == '__main__':
12 send_data(3)</span></span>
终端中输出如下:
ret = 2
n = 3
Traceback (most recent call last):
File "generator.py", line 12, in <module>
send_data(3)
File "generator.py", line 10, in send_data
g.send(n)
StopIteration
从输出中可以看出,ret获取了yield的值,而n的值则是第10行中send的参数值。generator
有send方法,send的值作为yield的返回值,如果没有send方法的话,yield返回None,另
一个值得注意的输出是,程序在第10行抛出了异常,这是send方法引起的,generator的send
方法会将调用方挂起,并且会唤醒被挂起的generator function,简单一点来说send的功能
相当于一次next调用,并且送去了一个值,这个值是yield语句的返回值。利用send唤醒被挂
起的generator function是tornado实现协程的核心。generator 能够调用send方法的一个前提是
这个generator function已经开始,也就是说这个generator function至少目前为止是处于挂起状态的。
example 6:
<span style="font-size:18px;"> 1 #coding: utf-8
2 def gen6():
3 n = yield 2
4 print 'n = ', n
5
6 def throw():
7 g = gen6()
8 ret = g.next()
9 print "ret = ", ret
10 g.throw(IndexError, IndexError('test for generator throw method'), None)
11 if __name__ == '__main__':
12 throw()</span></span>
终端中输出如下:
ret = 2
Traceback (most recent call last):
File "generator.py", line 12, in <module>
throw()
File "generator.py", line 10, in throw
g.throw(IndexError, IndexError('test for generator throw method'), None)
File "generator.py", line 3, in gen6
n = yield 2
IndexError: test for generator throw method
从输出的异常信息中可以看到,第10行使挂起的generator function产生了一次,generator 有throw
函数,使用该函数可以向generator function抛出一个异常throw的三个参数分别是type, type-value,
traceback。和sys.exc_info()函数返回的三个值是一致的。