奇妙的流控制 Python中的迭代器与生成器(1)


在Python 2.2中引进了一种带有新关键字的新型构造。这种构造是生成器;关键字是yield。生成器使几个新型、强大和富有表现力的编程习惯用法成为可能,但初看,要理解生成器,还是有一点困难。

51CTO推荐阅读:深入了解Python暂缓列表生成器

由于迭代器比较容易理解,让我们先来看它。基本上, 迭代器是含有 .next() 方法的对象。唔,这样定义不十分正确,但非常接近。事实上,当迭代器应用新的 iter() 内置函数时,大多数迭代器的上下文希望得到一个可以生成迭代器的对象。为使用户定义的类该类含有必不可少的 .next() 方法)返回迭代器,需要使 __iter__() 方法返回 self 。本文中的示例会清楚地说明这一点。如果迭代有一个逻辑终止,则迭代器的 .next() 方法可能决定抛出 StopIteration 异常。

生成器要稍微复杂和一般化一点。但生成器最典型的用途是用来定义迭代器;所以不值得总是为一些细微之处而担心。 生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。

在某些方面,生成器就象本专栏前面文章讨论的函数型编程中的“终止”。象“终止”一样,生成器“记住”了它数据状态。但生成器比“终止”要更进一步:生成器还“记住”了它在流控制构造在命令式编程中,这种构造不只是数据值)中的位置。由于连续性使您在执行框架间任意跳转,而不总是返回到直接调用者的上下文如同生成器那样),因此它仍是比较一般的。幸运的是,使用生成器比理解程序流和状态的所有概念性问题容易得多。实际上,稍加实践之后,就可以象普通函数那样容易地使用生成器。

随机遍历

让我们考虑一个相当简单的问题,可以用多种方法来解决它 ― 新方法和旧方法都可以。假设我们想要一串正的随机数字流,它比服从向后参考约束的数字流要小。明确的讲,我们希望每个后续数字比前一个数字至少大或小 0.4。而且,数字流本身不是无限的,在几个随机步骤后结束。这个示例中,当数字流中产生小于 0.1 的数字时,我们将简单地结束它。上述的约束有点象可以在“随机遍历”算法找到的约束,结束条件类似“统计”或“局部最小值”结果 ― 但当然,这要比大多数现实世界中简单。在 Python 2.1或更早的版本中,我们有几种方法来解决这个问题。一种方法是,简单地生成流中的数字列表并返回它。可能看起来象:

  1. RandomWalk_List.py  
  2. import  
  3. random  
  4. def  
  5. randomwalk_list  
  6. ():  
  7. last, rand = 1, random.random()   
  8. # init candidate elements  
  9. nums = []     
  10. # empty list  
  11. while  
  12. rand > 0.1:   
  13. # threshhold terminator  
  14. if  
  15. abs(last-rand) >= 0.4:     
  16. # accept the number  
  17.   last = rand 
  18.   nums.append(rand)     
  19. # add latest candidate to nums  
  20. else  
  21. :  
  22. print  
  23. '*',  
  24. # display the rejection  
  25. rand = random.random()    
  26. # new candidate  
  27. nums.append(rand)   
  28. # add the final small element  
  29. return  
  30. nums 

利用这个函数就象如下所示般简单:

  1. 随机遍历列表的迭代  
  2. for num in randomwalk_list():  
  3. print num, 

上面这种方法中有几个值得注意的局限性。这个特定的示例中极不可能产生庞大的数字列表,但只通过将阀值终结符定义得较严格,就可以创建任意大流随机精确大小,但可以预见数量级)。在某种程度上,内存和性能问题可能使得这种方法不切实际,以及没有必要。同样是这个问题,使得 Python 较早的版本中添加了 xrange() 和 xreadlines() 。更重要的是,许多流取决于外部事件,并且当每个元素可用时,才处理这些流。例如,流可以侦听一个端口,或者等待用户输入。试图在流之外创建完整的列表并不就是这些情形中的某一种。

在 Python 2.1 和较早版本中,我们的诀窍是使用“静态”函数局部变量来记住关于函数的上一次调用的一些事情。显而易见,全局变量可以做同样的工作,但它们带来了大家熟知的全局性名称空间污染的问题,并会因非局部性而引起错误。这里,如果您不熟悉这个诀窍,可能会感到诧异 ― Python 没有“正式”的静态范围声明。然而,如果赋予了命名参数可变的缺省值,那么参数就可以,用作以前调用的持久存储器。明确的讲,列表是一些便利的可变对象,他们甚至可以方便地保留多个值。使用“静态”方法,可以编写如下的函数:

  1. RandomWalk_Static.py  
  2. import  
  3. random  
  4. def  
  5. randomwalk_static  
  6. (last=[1]):  
  7. # init the "static" var(s)  
  8. rand = random.random()  
  9. # init a candidate value  
  10. if  
  11. last[0] < 0.1:   
  12. # threshhold terminator  
  13. return  
  14. None     
  15. # end-of-stream flag  
  16. while  
  17. abs(last[0]-rand) < 0.4:    
  18. # look for usable candidate  
  19. print  
  20. '*',  
  21. # display the rejection  
  22. rand = random.random()    
  23. # new candidate  
  24. last[0] = rand  
  25. # update the "static" var  
  26. return  
  27. rand 

这个函数是十分友好的存储器。它只需要记住一个以前的值,返回一个单个数字不是一个数字的大列表)。并且与此类似的一个函数可以返回取决于部分地或完全地)外部事件的连续的值。不利的一面是,利用这个函数有点不够简练,且相当不灵活。

  1. 静态随机遍历的迭代  
  2. num = randomwalk_static()  
  3. while num is not None:  
  4. print num,  
  5. num = randomwalk_static() 


评论关闭