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

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

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

当前回答

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

其他回答

当从一个可变对象(比如一个列表)创建一个生成器时,请注意生成器将在使用生成器时根据列表的状态进行计算,而不是在创建生成器时:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

如果你的列表有可能被修改(或者列表中的一个可变对象),但你需要生成器创建时的状态,你需要使用列表理解。

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

列表推导式是热切的,但生成器是懒惰的。

在列表推导式中,所有对象都是立即创建的,它需要更长的时间来创建和返回列表。在生成器表达式中,对象创建被延迟到next()请求。在next()生成器对象创建并立即返回时。

在列表推导中迭代更快,因为已经创建了对象。

如果迭代列表解析和生成器表达式中的所有元素,时间性能大致相同。即使生成器表达式立即返回生成器对象,它也不会创建所有元素。每次迭代一个新元素时,它都会创建并返回它。

But if you do not iterate through all the elements generator are more efficient. Let's say you need to create a list comprehensions that contains millions of items but you are using only 10 of them. You still have to create millions of items. You are just wasting time for making millions of calculations to create millions of items to use only 10. Or if you are making millions of api requests but end up using only 10 of them. Since generator expressions are lazy, it does not make all the calculations or api calls unless it is requested. In this case using generator expressions will be more efficient.

在列表推导式中,整个集合被加载到内存中。但是生成器表达式,一旦它在下一次()调用时返回一个值给你,它就完成了,不需要再将它存储在内存中。只有一个项目被载入内存。如果你在磁盘上迭代一个巨大的文件,如果文件太大,你可能会遇到内存问题。在这种情况下,使用生成器表达式更有效。

我正在使用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是一个很好的例子,可以使用生成器的所有优点。

关于内置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