在Python中什么时候应该使用生成器表达式,什么时候应该使用列表推导式?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]

当前回答

遍历生成器表达式或列表推导式也会做同样的事情。但是,列表推导式将首先在内存中创建整个列表,而生成器表达式将动态地创建项,因此您可以将其用于非常大的(也是无限的!)序列。

其他回答

有时候你可以在itertools中使用tee函数,它会为同一个生成器返回多个迭代器,这些迭代器可以独立使用。

我正在使用Hadoop Mincemeat模块。我认为这是一个值得注意的好例子:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

在这里,生成器从一个文本文件(最大15GB)中获取数字,并使用Hadoop的map-reduce对这些数字应用简单的数学运算。如果我没有使用yield函数,而是使用一个列表理解,那么计算总和和平均值将花费更长的时间(更不用说空间复杂性了)。

Hadoop是一个很好的例子,可以使用生成器的所有优点。

John的回答很好(当您想要多次迭代某个内容时,列表推导式更好)。然而,同样值得注意的是,如果您想使用任何列表方法,则应该使用列表。例如,下面的代码将无法工作:

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

基本上,如果你所做的只是迭代一次,就使用生成器表达式。如果希望存储和使用生成的结果,那么最好使用列表推导式。

由于性能是最常见的选择一个而不是另一个的原因,我的建议是不要担心,只选择一个;如果您发现您的程序运行得太慢,那么只有在这时,您才应该返回并考虑调优您的代码。

生成器表达式的好处是它使用更少的内存,因为它不会一次构建整个列表。生成器表达式最好在列表作为中介时使用,例如对结果求和,或从结果中创建字典。

例如:

sum(x*2 for x in xrange(256))

dict( (k, some_func(k)) for k in some_list_of_keys )

这样做的好处是列表不是完全生成的,因此占用的内存很少(而且应该更快)。

但是,当期望的最终产品是一个列表时,应该使用列表推导式。使用生成器表达式不会节省任何内存,因为您需要生成的列表。您还可以使用任何列表函数,如sorted或reversed。

例如:

reversed( [x*2 for x in xrange(256)] )

关于内置Python函数的一些注意事项:

如果需要利用任何或全部的短路行为,请使用生成器表达式。这些函数被设计为在已知答案时停止迭代,但是列表推导式必须在调用函数之前计算每个元素。

例如,如果我们有

from time import sleep
def long_calculation(value):
    sleep(1) # for simulation purposes
    return value == 1

然后any([long_calculation(x) for x in range(10)])大约需要10秒,因为long_calculation将为每个x调用,any(long_calculation(x) for x in range(10))只需要大约2秒,因为long_calculation只会在0和1输入时被调用。

当any和all遍历列表理解时,一旦已知答案,它们仍然会停止检查元素的真实性(只要any发现一个真结果,或者all发现一个假结果);然而,与理解所做的实际工作相比,这通常是微不足道的。

生成器表达式当然更节省内存,如果可能的话。使用非短路的min, max和sum (max的计时如图所示),列表推导会稍微快一些:

$ python -m timeit "max(_ for _ in range(1))"
500000 loops, best of 5: 476 nsec per loop
$ python -m timeit "max([_ for _ in range(1)])"
500000 loops, best of 5: 425 nsec per loop
$ python -m timeit "max(_ for _ in range(100))"
50000 loops, best of 5: 4.42 usec per loop
$ python -m timeit "max([_ for _ in range(100)])"
100000 loops, best of 5: 3.79 usec per loop
$ python -m timeit "max(_ for _ in range(10000))"
500 loops, best of 5: 468 usec per loop
$ python -m timeit "max([_ for _ in range(10000)])"
500 loops, best of 5: 442 usec per loop