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

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

# List comprehension
[x*2 for x in range(256)]
有帮助吗?

解决方案

约翰的答案很好(当你想多次迭代某件事时,列表理解会更好)。但是,还值得注意的是,如果您想使用任何列表方法,则应该使用列表。例如,以下代码将不起作用:

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

基本上,如果您所做的只是迭代一次,请使用生成器表达式。如果您想存储和使用生成的结果,那么使用列表理解可能会更好。

由于性能是选择其中之一的最常见原因,因此我的建议是不要担心它,只选择一个;如果你发现你的程序运行得太慢,那么你才应该回去担心调整你的代码。

其他提示

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

当结果需要多次迭代或速度至关重要时,请使用列表推导式。在范围较大或无限的情况下使用生成器表达式。

重要的一点是列表理解创建一个新列表。生成器创建一个可迭代对象,它将在您使用这些位时动态“过滤”源材料。

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

因此,您尝试从编写列表理解开始:

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

这会吸收整个文件,处理每一行,并将匹配的行存储在数组中。因此,该数组最多可包含 2TB 的内容。这是很大的内存,可能不适合您的用途。

因此,我们可以使用生成器对我们的内容应用“过滤器”。在我们开始迭代结果之前,不会实际读取任何数据。

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 迭代器,依次读取文件。

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

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

例如:

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

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

这样做的优点是列表没有完全生成,因此使用的内存很少(并且应该更快)

但是,当所需的最终产品是列表时,您应该使用列表推导式。您不会使用生成器表达式保存任何内存,因为您需要生成的列表。您还可以获得能够使用任何列表功能(例如排序或反转)的好处。

例如:

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

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

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

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

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

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 是利用 Generator 的所有优点的一个很好的例子。

有时你可以逃脱 球座 函数来自 迭代工具, ,它为同一个生成器返回多个可以独立使用的迭代器。

如何使用 [(exp for x in iter)] 来获得两者的好处。生成器理解和列表方法的性能

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top