Jim's blog -- Focus On ERP/.NET/BI

Dynamics AX ERP, .NET, Business Intelligence

导航

生成器方法漫谈(generator function)

生成器方法(generator function)作为一个可选特性在Python2.2中首次出现,2.3版本中内置支持了此特性,yield成为了关键字,生成器在后续版本中得到增强(比如增加了异常处理等特性)。C#2.0中也引入类似特性(迭代器),这两者之间有不少相似之处。本文针对IronPython 2.0 beta3进行讨论。


任何包含yield表达式的函数即为生成器方法,同时yield也只在定义一个生成器方法时使用。生成器方法是一个特殊的方法,当你调用生成器方法时,返回的不是像普通函数一样的单一值,而是返回了一个迭代器(相当于C#中的System.Collections.IEnumerator接口)。yield表达式会返回一个值:val = (yield i) ,val的值即为表达式的值。但是这个值并不是i,这个值是由调用生成器的send(value)方法指定的。如果不调用此方法或者调用send(None)方法,则返回值为None。而迭代器本身的返回值则是由yield语句(注意yield表达式[val = (yield i)]和yield语句[yield i]的区别)指定的,当每次调用迭代器的next()/send()方法时,yield语句会立即返回一个值给调用方,同时会保存当前代码执行位置及现场状态,并暂停执行(此时yield表达式还未被执行,即val没被赋值)。当再次调用迭代器的方法时将从此位置(yield表达式)恢复执行直到再次遇到下一个yield表达式。

下面将根据生成器支持的操作方法具体介绍:

next()

>>> def generate_ints(N):
     
for i in range(N):
             
yield i

>>> gen=generate_ints(3)
>>> gen
<generator object at 0x000000000000002B>
>>> gen.next()
0
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
Traceback (most recent call last):
  File 
"<stdin>", line 1in <module>
StopIteration: 引发类型为“IronPython.Runtime.Exceptions.StopIterationException
”的异常。
gen.next()调用后,generate_ints()开始执行,返回yield语句右侧的i值,在yield表达式处暂停(本例忽略了表达式的值,只使用了yield语句)。当再次调用gen.next()后,从暂停处会继续执行直到下一个yield表达式。当我们第4次调用gen.next()时由于已经没有下一个yield表达式所以拋出一个StopIteration异常,指示迭代器结束。

send(value)
>>> def counter (maximum):
     i
=0
     
while i < maximum:
             val 
= (yield i)
             
#如果设置了yield表达式的值,则将改变计数器的值
             if val is not None:
                     i
=val
             
else:
                     i
+=1

>>> it=counter(10)
>>> print it.next()
0
>>> print it.send(8)
8
>>> print it.next()
9
>>> print it.next()
Traceback (most recent call last):
  File 
"<stdin>", line 1in <module>
StopIteration: 引发类型为“IronPython.Runtime.Exceptions.StopIterationException
”的异常。
>>>
我们可以看到yield表达式的返回值是由send(value)中的value指定的。next()/send()方法都能使生成器继续执行并返回yield语句右侧的值,不同点在于next()是不带参数的,所以也就不可以设置yield表达式的返回值(默认值为None)。我们也可以调用send(None),这样两者的功能是等价的。
注意:首次调用时,不可以用send()方法去赋一个非None值,因为这个值没有yield表达式去接收,否则会引发一个TypeError异常:
Traceback (most recent call last):
  File 
"<stdin>", line 1in <module>
TypeError: can
't send non-None value to a just-started generator

throw( type[, value[, traceback]])
throw(type, value=None, traceback=None)用来在生成器内部引发一个异常。异常在生成器暂停执行处由yield表达式引发。我们知道当生成器缺少yield表达式而结束的时候会拋出一个StopIteration异常而结束,我们也可以用throw()来传入一个异常给生成器,从而控制生成器的执行。当在生成器内部没有对传入的异常进行捕获处理时,异常将返回给调用者。
>>> def throw():
     i
=0
     
while True:
             i
+=1
             
try:
                     
yield i
             
except StopIteration, v:
                     
print "Caught StopIteration"
                     
#这里是为了示例在生成器里如何捕获throw()传进的异常,
                     #所以还把异常返回给调用方,结束生成器
                     raise v

>>> g=throw()
>>> g.next()
1
>>> g.next()
2
>>> g.throw(StopIteration)
Caught StopIteration
Traceback (most recent call last):
  File 
"<stdin>", line 1in <module>
StopIteration
>>> g.next()
Traceback (most recent call last):
  File 
"<stdin>", line 1in <module>
StopIteration: 引发类型为“IronPython.Runtime.Exceptions.StopIterationException
”的异常。
>>>
首先定义了一个无穷的生成器函数,在正常调用next()方法的情况下会一直有返回值,当调用了throw(StopIteration)传入一个结束迭代器异常后,于是这个异常被生成器捕获并返回给调用者使生成器结束,当再调用next()方法时,引发了StopIteration异常。

close()
有了上面的throw()方法的介绍后,现在理解close()就容易多了。此方法会在生成器暂停执行处抛出GeneratorExit异常。当在生成器内部捕获到此异常后必须重新抛出GeneratorExit或者StopIteration异常以使生成器正常结束,如果没有重新抛出而是返回一个值的话将触发RuntimeError异常。对一个生成器多次调用close()不会产生错误。

posted on 2008-07-23 15:08  季方亮  阅读(2605)  评论(0编辑  收藏  举报