什么时候应该使用生成器表达式?什么时候应该在Python中使用列表推导?
# Generator expression
(x*2 for x in range(256))
# List comprehension
[x*2 for x in range(256)]
答案 0 :(得分:251)
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
基本上,如果您所做的只是迭代一次,请使用生成器表达式。如果你想存储和使用生成的结果,那么你可能最好使用列表理解。
由于表演是选择其中一个的最常见原因,我的建议是不要担心它而只选择一个;如果您发现您的程序运行速度太慢,那么只有这样您才会回过头来担心调整代码。
答案 1 :(得分:161)
迭代生成器表达式或 list comprehension 将执行相同的操作。但是, list comprehension 将首先在内存中创建整个列表,而生成器表达式将动态创建项目,因此您可以将其用于非常大的(也是无限的!)序列。
答案 2 :(得分:90)
当结果需要多次迭代或速度至关重要时,请使用列表推导。使用范围很大或无限的生成器表达式。
答案 3 :(得分:52)
重点是列表理解会创建一个新列表。生成器创建一个可迭代的对象,它将过滤"过滤"消耗这些位时动态的源材料。
想象一下,你有一个名为" 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
迭代器请求行(按指示过滤),然后迭代器读取文件。
所以而不是"推动"数据以完全填充的列表形式输出到输出函数,您可以通过输出函数的方式来“拉”"数据仅在需要时。在我们的情况下,这更有效,但不太灵活。发电机是单向的,一次通过;我们读过的日志文件中的数据会被立即丢弃,因此我们无法返回上一行。另一方面,我们不必担心在我们完成数据后保留数据。
答案 4 :(得分:44)
生成器表达式的好处是它使用更少的内存,因为它不会立即构建整个列表。当列表是中介时,最好使用生成器表达式,例如对结果求和,或者从结果中创建一个字典。
例如:
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)] )
答案 5 :(得分:10)
当从可变对象(如列表)创建生成器时,请注意在使用生成器时,生成器将在列表状态上进行评估,而不是在创建生成器时:
>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing
如果您的列表有可能被修改(或该列表中的可变对象),但您需要创建生成器时的状态,则需要使用列表推导。
答案 6 :(得分:4)
我正在使用Hadoop Mincemeat module。我认为这是一个很好的例子,可以记下:
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是使用Generators的所有优点的一个很好的例子。
答案 7 :(得分:3)
有时你可以使用itertools中的 tee 函数,它会返回可以独立使用的同一个生成器的多个迭代器。
答案 8 :(得分:0)
如何使用[(exp in iter in x))获得两者的好处。生成器理解以及列表方法带来的性能
答案 9 :(得分:0)
答案 10 :(得分:0)
列表推导式是急切的,但生成器是懒惰的。
在列表推导式中,所有对象都是立即创建的,创建和返回列表需要更长的时间。在生成器表达式中,对象创建被延迟到 next()
请求。 next()
生成器对象被创建并立即返回。
列表推导式中的迭代速度更快,因为对象已经创建。
如果迭代列表推导式和生成器表达式中的所有元素,时间性能大致相同。即使生成器表达式立即返回生成器对象,它也不会创建所有元素。每次迭代一个新元素时,它都会创建并返回它。
但是如果不遍历所有元素,生成器的效率会更高。假设您需要创建一个包含数百万个项目的列表推导式,但您只使用了其中的 10 个。您仍然需要创建数百万个项目。您只是在浪费时间进行数百万次计算以创建数百万个项目而仅使用 10 个。或者如果您发出数百万个 api 请求但最终只使用其中的 10 个。由于生成器表达式是惰性的,除非被请求,否则它不会进行所有计算或 api 调用。在这种情况下,使用生成器表达式会更有效。
在列表推导中,整个集合被加载到内存中。但是生成器表达式,一旦它在您的 next()
调用时向您返回一个值,它就完成了并且不再需要将它存储在内存中。只有一个项目被加载到内存中。如果您在磁盘上迭代一个巨大的文件,如果文件太大,您可能会遇到内存问题。在这种情况下,使用生成器表达式更有效。