在Python中什么时候应该使用生成器表达式,什么时候应该使用列表推导式?
# Generator expression
(x*2 for x in range(256))
# List comprehension
[x*2 for x in range(256)]
在Python中什么时候应该使用生成器表达式,什么时候应该使用列表推导式?
# Generator expression
(x*2 for x in range(256))
# List comprehension
[x*2 for x in range(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迭代器读取文件。
因此,您不是以完全填充的列表的形式将数据“推”到输出函数中,而是为输出函数提供了一种仅在需要时“拉”数据的方法。在我们的例子中,这更有效,但不那么灵活。生成器是单向的,一遍;我们读取的日志文件中的数据立即被丢弃,因此我们不能返回到前一行。另一方面,一旦我们处理完数据,我们就不必担心如何保存数据。
其他回答
重要的一点是,列表推导式创建了一个新列表。生成器创建一个可迭代对象,该对象将在您使用比特时实时“过滤”源材料。
假设您有一个名为“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迭代器读取文件。
因此,您不是以完全填充的列表的形式将数据“推”到输出函数中,而是为输出函数提供了一种仅在需要时“拉”数据的方法。在我们的例子中,这更有效,但不那么灵活。生成器是单向的,一遍;我们读取的日志文件中的数据立即被丢弃,因此我们不能返回到前一行。另一方面,一旦我们处理完数据,我们就不必担心如何保存数据。
当结果需要多次迭代时,或者在速度非常重要的情况下,使用列表推导式。在范围较大或无穷大的地方使用生成器表达式。
有关更多信息,请参阅生成器表达式和列表推导式。
列表推导式是热切的,但生成器是懒惰的。
在列表推导式中,所有对象都是立即创建的,它需要更长的时间来创建和返回列表。在生成器表达式中,对象创建被延迟到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.
在列表推导式中,整个集合被加载到内存中。但是生成器表达式,一旦它在下一次()调用时返回一个值给你,它就完成了,不需要再将它存储在内存中。只有一个项目被载入内存。如果你在磁盘上迭代一个巨大的文件,如果文件太大,你可能会遇到内存问题。在这种情况下,使用生成器表达式更有效。
关于内置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
遍历生成器表达式或列表推导式也会做同样的事情。但是,列表推导式将首先在内存中创建整个列表,而生成器表达式将动态地创建项,因此您可以将其用于非常大的(也是无限的!)序列。