奇妙的流控制 Python中的迭代器与生成器(1)
奇妙的流控制 Python中的迭代器与生成器(1)
在Python 2.2中引进了一种带有新关键字的新型构造。这种构造是生成器;关键字是yield。生成器使几个新型、强大和富有表现力的编程习惯用法成为可能,但初看,要理解生成器,还是有一点困难。
51CTO推荐阅读:深入了解Python暂缓列表生成器
由于迭代器比较容易理解,让我们先来看它。基本上, 迭代器是含有 .next() 方法的对象。唔,这样定义不十分正确,但非常接近。事实上,当迭代器应用新的 iter() 内置函数时,大多数迭代器的上下文希望得到一个可以生成迭代器的对象。为使用户定义的类该类含有必不可少的 .next() 方法)返回迭代器,需要使 __iter__() 方法返回 self 。本文中的示例会清楚地说明这一点。如果迭代有一个逻辑终止,则迭代器的 .next() 方法可能决定抛出 StopIteration 异常。
生成器要稍微复杂和一般化一点。但生成器最典型的用途是用来定义迭代器;所以不值得总是为一些细微之处而担心。 生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。
在某些方面,生成器就象本专栏前面文章讨论的函数型编程中的“终止”。象“终止”一样,生成器“记住”了它数据状态。但生成器比“终止”要更进一步:生成器还“记住”了它在流控制构造在命令式编程中,这种构造不只是数据值)中的位置。由于连续性使您在执行框架间任意跳转,而不总是返回到直接调用者的上下文如同生成器那样),因此它仍是比较一般的。幸运的是,使用生成器比理解程序流和状态的所有概念性问题容易得多。实际上,稍加实践之后,就可以象普通函数那样容易地使用生成器。
随机遍历
让我们考虑一个相当简单的问题,可以用多种方法来解决它 ― 新方法和旧方法都可以。假设我们想要一串正的随机数字流,它比服从向后参考约束的数字流要小。明确的讲,我们希望每个后续数字比前一个数字至少大或小 0.4。而且,数字流本身不是无限的,在几个随机步骤后结束。这个示例中,当数字流中产生小于 0.1 的数字时,我们将简单地结束它。上述的约束有点象可以在“随机遍历”算法找到的约束,结束条件类似“统计”或“局部最小值”结果 ― 但当然,这要比大多数现实世界中简单。在 Python 2.1或更早的版本中,我们有几种方法来解决这个问题。一种方法是,简单地生成流中的数字列表并返回它。可能看起来象:
- RandomWalk_List.py
- import
- random
- def
- randomwalk_list
- ():
- last, rand = 1, random.random()
- # init candidate elements
- nums = []
- # empty list
- while
- rand > 0.1:
- # threshhold terminator
- if
- abs(last-rand) >= 0.4:
- # accept the number
- last = rand
- nums.append(rand)
- # add latest candidate to nums
- else
- :
- '*',
- # display the rejection
- rand = random.random()
- # new candidate
- nums.append(rand)
- # add the final small element
- return
- nums
利用这个函数就象如下所示般简单:
- 随机遍历列表的迭代
- for num in randomwalk_list():
- print num,
上面这种方法中有几个值得注意的局限性。这个特定的示例中极不可能产生庞大的数字列表,但只通过将阀值终结符定义得较严格,就可以创建任意大流随机精确大小,但可以预见数量级)。在某种程度上,内存和性能问题可能使得这种方法不切实际,以及没有必要。同样是这个问题,使得 Python 较早的版本中添加了 xrange() 和 xreadlines() 。更重要的是,许多流取决于外部事件,并且当每个元素可用时,才处理这些流。例如,流可以侦听一个端口,或者等待用户输入。试图在流之外创建完整的列表并不就是这些情形中的某一种。
在 Python 2.1 和较早版本中,我们的诀窍是使用“静态”函数局部变量来记住关于函数的上一次调用的一些事情。显而易见,全局变量可以做同样的工作,但它们带来了大家熟知的全局性名称空间污染的问题,并会因非局部性而引起错误。这里,如果您不熟悉这个诀窍,可能会感到诧异 ― Python 没有“正式”的静态范围声明。然而,如果赋予了命名参数可变的缺省值,那么参数就可以,用作以前调用的持久存储器。明确的讲,列表是一些便利的可变对象,他们甚至可以方便地保留多个值。使用“静态”方法,可以编写如下的函数:
- RandomWalk_Static.py
- import
- random
- def
- randomwalk_static
- (last=[1]):
- # init the "static" var(s)
- rand = random.random()
- # init a candidate value
- if
- last[0] < 0.1:
- # threshhold terminator
- return
- None
- # end-of-stream flag
- while
- abs(last[0]-rand) < 0.4:
- # look for usable candidate
- '*',
- # display the rejection
- rand = random.random()
- # new candidate
- last[0] = rand
- # update the "static" var
- return
- rand
这个函数是十分友好的存储器。它只需要记住一个以前的值,返回一个单个数字不是一个数字的大列表)。并且与此类似的一个函数可以返回取决于部分地或完全地)外部事件的连续的值。不利的一面是,利用这个函数有点不够简练,且相当不灵活。
- 静态随机遍历的迭代
- num = randomwalk_static()
- while num is not None:
- print num,
- num = randomwalk_static()
评论关闭