美中不足

上述方法对于手边的问题非常好用。但没有一种方法能很好地解决这样的情形:例程在运行中创建了大量的局部变量,并把它的运行简化为循环和条件的嵌套。如果带静态或全局)变量的迭代器类或函数取决于多个数据状态,则出现两个问题。一个是一般性问题:创建多个实例属性或静态列表元素来保留每个数据值。更为重要的问题是计算如何确切地返回到与数据状态相符的流逻辑的相关部分。非常容易忘记不同数据间的相互作用和互相依存。

生成器完全绕过了整个问题。生成器“返回”时带关键字 yield ,但“记住”了它“返回”的所有确切执行位置。下次调用生成器时,它再接着上次的位置 — 包括函数流和变量值这两个方面。

在 Python 2.2+ 中,不直接 写生成器。相反,编写一个函数,当调用它时,返回生成器。这可能看起来有点古怪,但“函数工厂”是 Python 的常见特性,并且“生成器工厂”明显是这个概念性扩展。在 Python 2.2+ 中使函数成为生成器工厂是它主体某处的一个或多个 yield 语句。如果 yield 发生, return 一定只发生在没有伴随任何返回值的情况中。然而,一个较好的选择是,安排函数体以便于完成所有 yield 之后,执行就“跳转到结束”。但如果遇到 return ,它导致产生的生成器抛出 StopIteration 异常,而不是进一步生成值。

从我的观点来看,过去对生成器工厂的语法选择有点欠缺。 yield 语句可以非常好地存在于函数体中,您可能无法确定是否函数一定会在函数体最初 N 行内的某处作为生成器工厂而存在。当然,对于函数工厂,也存在这样的问题,但是由于函数工厂不改变函数体的实际 语法并且有时允许函数体返回普通值,尽管这可能不是出自良好的设计)。对于我来说,新关键字 ― 比如 generator 代替 def ― 会是一个比较好的选择。

先不考虑语法,当调用生成器来担当迭代器时,生成器有良好的状况来自动担当迭代器。这里不需要象类的 .__iter__() 方法。遇到的每个 yield 都成为生成器的 .next() 方法的返回值。为了清楚起见,我们来看一个最简单的生成器:

  1. 最简单可行的 Python 2.2 生成器  
  2. >>>   
  3. from  
  4. __future__   
  5. import  
  6. generators  
  7. >>>   
  8. def  
  9. gen  
  10. ():  
  11. yield 1  
  12. >>> g = gen()  
  13. >>> g.next()  
  14. 1  
  15. >>> g.next()  
  16. Traceback (most recent call last):  
  17. File "<pyshell#15>", line 1,   
  18. in  
  19. ?  
  20. g.next()  
  21. StopIteration 

让我们使生成器工作在我们样本问题中:

  1. RandomWalk_Generator.py  
  2. from  
  3. __future__   
  4. import  
  5. generators     
  6. # only needed for Python 2.2  
  7. import  
  8. random  
  9. def  
  10. randomwalk_generator  
  11. ():  
  12. last, rand = 1, random.random()   
  13. # initialize candidate elements  
  14. while  
  15. rand > 0.1:   
  16. # threshhold terminator  
  17. print  
  18. '*',  
  19. # display the rejection  
  20. if  
  21. abs(last-rand) >= 0.4:     
  22. # accept the number  
  23.   last = rand     
  24. # update prior value  
  25.   yield rand  
  26. # return AT THIS POINT  
  27. rand = random.random()    
  28. # new candidate  
  29. yield rand    
  30. # return the final small element 

这个定义的简单性是吸引人的。可以手工或者作为迭代器来利用这个生成器。在手工情形下,生成器可以在程序中传递,并且无论在哪里以及无论何时需要这非常灵活),都可以调用。手工情形的一个简单示例是:

  1. 随机遍历生成器的手工使用  
  2. gen = randomwalk_generator()  
  3. try:  
  4. while 1: print gen.next(),  
  5. except StopIteration:  
  6. pass 

然而,更多情况下,可能将生成器作为迭代器来使用,这样更为简练并且看起来又象只是一个老式的序列):

  1. 作为迭代器的随机遍历生成器  
  2. for num in randomwalk_generator():  
  3. print_short(num) 

结束语

Python 程序员需要花一点时间来熟悉生成器的来龙去脉。最初这样一个简单构造所增加的能力是令人惊奇的;并且我预言,甚至熟练的程序员象 Python 开发人员自己)也需要花一些时间来继续发现使用生成器过程中的一些微妙的新技术。


评论关闭