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

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

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

当结果需要多次迭代时,或者在速度非常重要的情况下,使用列表推导式。在范围较大或无穷大的地方使用生成器表达式。

有关更多信息,请参阅生成器表达式和列表推导式。


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


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

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

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


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


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

例如:

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)] )

重要的一点是,列表推导式创建了一个新列表。生成器创建一个可迭代对象,该对象将在您使用比特时实时“过滤”源材料。

假设您有一个名为“hugefile.txt”的2TB日志文件,您需要以单词“ENTRY”开头的所有行的内容和长度。

所以你可以试着从写一个列表理解开始:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

这将获取整个文件,处理每一行,并将匹配的行存储在数组中。因此,这个数组最多可以包含2TB的内容。这是一个很大的RAM,可能不适合您的目的。

因此,我们可以使用生成器对内容应用“过滤器”。直到我们开始对结果进行迭代,才实际读取数据。

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

我们的文件连一行都没读过。事实上,假设我们想进一步过滤结果:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

仍然没有读取任何数据,但是现在我们已经指定了两个生成器,它们将按照我们的意愿处理数据。

让我们把过滤后的行写入另一个文件:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

现在我们读取输入文件。当我们的for循环继续请求额外的行时,long_entries生成器要求来自entry_lines生成器的行,只返回长度大于80个字符的行。然后,entry_lines生成器从logfile迭代器请求行(按指示过滤),然后logfile迭代器读取文件。

因此,您不是以完全填充的列表的形式将数据“推”到输出函数中,而是为输出函数提供了一种仅在需要时“拉”数据的方法。在我们的例子中,这更有效,但不那么灵活。生成器是单向的,一遍;我们读取的日志文件中的数据立即被丢弃,因此我们不能返回到前一行。另一方面,一旦我们处理完数据,我们就不必担心如何保存数据。


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


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

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

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


Python 3.7:

列表推导更快。

生成器的内存效率更高。

正如其他人所说,如果你想要扩展无限的数据,你最终还是需要一个生成器。对于相对静态的、需要快速处理的中小型工作,最好是对清单进行理解。


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

在列表推导式中,所有对象都是立即创建的,它需要更长的时间来创建和返回列表。在生成器表达式中,对象创建被延迟到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.

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


我认为大多数答案都忽略了一点。列表推导式基本上创建一个列表并将其添加到堆栈中。在列表对象非常大的情况下,脚本进程将被杀死。在这种情况下,生成器更受欢迎,因为它的值不存储在内存中,而是存储为有状态函数。还有创造速度;列表理解比生成器理解慢

简而言之, 当obj的大小不是很大时,使用列表推导式,否则使用生成器推导式


对于函数式编程,我们希望使用尽可能少的索引。因此,如果我们想在获取元素的第一个切片后继续使用元素,islice()是一个更好的选择,因为迭代器状态会被保存。

from itertools import islice

def slice_and_continue(sequence):
    ret = []
    seq_i = iter(sequence) #create an iterator from the list

    seq_slice = islice(seq_i,3) #take first 3 elements and print
    for x in seq_slice: print(x),

    for x in seq_i: print(x**2), #square the rest of the numbers

slice_and_continue([1,2,3,4,5])

输出:1 2 3 16 25


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