生成器表达式与列表推导式

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

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


# List comprehension
[x*2 for x in range(256)]
181031 次浏览

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

更多信息请参见生成器表达式和列表推导式

迭代生成器表达式列表理解也会做同样的事情。然而,列表理解将首先在内存中创建整个列表,而生成器表达式将在运行中创建项,因此你可以将它用于非常大的(也是无限的!)序列。

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

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中的三通函数,它会为同一个生成器返回多个可以独立使用的迭代器。

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

例如:

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迭代器请求行(按指示过滤),该迭代器接着读取文件。

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

我正在使用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:

列表推导更快。

enter image description here

生成器的内存效率更高。 enter image description here < / p >

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

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

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

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

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

但如果你不遍历所有的元素生成器是更有效的。假设您需要创建一个包含数百万项的列表推导式,但您只使用其中的10项。你仍然需要创造数百万个道具。你只是在浪费时间,进行数百万次计算,创建数百万个项目,只使用10个。或者,如果你发出了数百万个api请求,但最终只使用了其中的10个。由于生成器表达式是惰性的,它不会进行所有的计算或api调用,除非它被请求。在这种情况下,使用生成器表达式将更有效。

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

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

< p >在短; 当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函数的一些注意事项:

如果需要利用__ABC0或all的短路行为,请使用生成器表达式。这些函数被设计为在已知答案时停止迭代,但是列表推导式必须在调用函数之前计算每个元素。(anyall在得到答案时仍然会停止检查元素是否为真实与否,给定一个来自列表推导式的列表;然而,与实际计算元素的工作相比,这通常是微不足道的。)

生成器表达式当然在使用时更节省内存。使用非短路的minmaxsum(这里显示了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