深入了解Python暂缓列表生成器(1)


有的时候你的程序精力过剩,把你不需要或者不希望它做的事情都给做了——你想要它懒一点才好。这就是生成器的用武之地。使用python的生成器generator)能够让你精确地决定要它做多少以及什么时候去做。

本文将介绍python生成器,它可以一段一段地构成一个序列,按照你的要求完成工作量。

这被叫做暂缓求值lazy evaluation),用来推迟某个特定值的计算,直到程序某个点需要才开始。暂缓求值在很多类型的编程中非常有用,因为它不用计算从来都不使用的值,因而可以带来性能上的提升。有的语言,比如Haskell和Miranda,在默认情况下都使用暂缓求值;而其他的语言,像Scheme、Ocaml和python,都可以通过专门的句法来使用暂缓求值。在python里,实现暂缓求值的方式是使用生成器。

利用生成器你可以编写出这样一个函数,它能够返回结果然后暂停,当你下一次调用这个函数的时候它会从暂停的地方恢复。你不需要任何专门的句法来编写生成器,要做的只是表示一下什么时候使用yield语句返回一个中间值。说明这一点的最简单方法是举一个例子:

>>> def generator1():...yield "first"...yield "second"...yield "third"... 
>>> gen = generator1()>>> gen >>> gen.next()'first'>>> gen.next()'second'
>>> gen.next()'third'
>>> gen.next()Traceback (most recent call last):File "", line 1, in ?StopIteration
>>>

在这个例子中,我们首先定义一个叫做generator1的生成器,它会生成三个值:“first”、“second”和“third”三个字符串。当我们创建一个新的生成器对象gen)时,它就开始执行函数。每当你调用生成器next方法时,它会继续执行这个函数,直到生成下一个值。当生成器达到块结尾的时候,或者碰到返回语句时,它会产生一个StopIteration异常。

生成器本质上就是迭代器,这也就是说它们可以用在很多表达式里,而不需要用序列列表等)。例如,你可以不使用上面的代码,而改用下面的:

>>> gen2 = generator1()>>> for i in gen2:...print i... firstsecondthird

类似的,你可以一次就把生成器转换成为列表:

>>> gen3 = generator1()>>> list(gen3)['first', 'second', 'third']

下面就让我们举一个真正派得上用场的例子。下面是一个相当标准的用于产生组合的函数,换句话说,一个序列可以以多种独特的方式被切割成小的序列:

def combination(seq, length):if not length: return [[]]
else:l = []for i in xrange(len(seq)):for result in combination(seq[i+1:], 
length-1):l += [[seq[i]]+result]return l

它的用法是:

>>> combination("ABCDE", 3)
[['A', 'B', 'C'], ['A', 'B', 'D'], ['A', 'B', 'E'],
['A', 'C', 'D'], ['A', 'C', 'E'], ['A', 'D', 'E'], 
['B', 'C', 'D'], ['B', 'C', 'E'], ['B', 'D', 'E'], ['C', 'D', 'E']]
>>> combination("ABCDE", 2)
[['A', 'B'], ['A', 'C'], ['A', 'D'], ['A', 'E'], ['B', 'C'],
['B', 'D'], ['B', 'E'], ['C', 'D'], ['C', 'E'], ['D', 'E']]
>>> combination("ABCDE", 5)
[['A', 'B', 'C', 'D', 'E']]

现在让我们改用生成器来做同样的事情。你想要在某些点往最终结果列表里加入一个值并用yield语句替换掉它,因此这里的技巧是简单地替换掉每一个这样的点。

def xcombination(seq,length):
if not length: yield []
else:
for i in xrange(len(seq)):
for result in xcombination(seq[i+1:], length-1):
yield [seq[i]]+result

现在我们用另一种方式获得了同样结果,唯一的不同是它只会按照你的需要来工作:

>>> comb = xcombination("ABCDE", 3)
>>> comb.next()
['A', 'B', 'C']
>>> comb.next()
['A', 'B', 'D']
>>> list(comb)
[['A', 'B', 'E'], ['A', 'C', 'D'], ['A', 'C', 'E'],

['A', 'D', 'E'], ['B', 'C', 'D'], ['B', 'C', 'E'],

['B', 'D', 'E'], ['C', 'D', 'E']]
>>> comb2 = xcombination("ABCDE",2)
>>> for i in xrange(3):
... print comb2.next()
... ['A', 'B']
['A', 'C']
['A', 'D']


评论关闭