众所周知,Python列表推导的工作原理比循环要快。但是,在某些情况下,它们可能会严重破坏程序的性能,甚至导致内存崩溃。在这些情况下,需要考虑使用生成器表达式。
从语法上讲,这两个非常相似。它们之间的唯一区别是,您可以使用声明列表推导[]和,使用来声明生成器表达式(),就像这样:
list_compr = [x**2 for x in range(10)]
gen_expr = (x**2 for x in range(10)
关键是要对列表理解进行评估。在交互式shell中定义列表理解后,我们将获得结果列表:
>>> [x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
相反,生成器表达式将返回生成器对象:
>>> (x**2 for x in range(10))
<generator object <genexpr> at 0x0000023DD840F7C8>
生成器正常运行,需要使用next()方法,迭代生成器表达式或使用类似list(),set()或tuple()方法。
在上面的示例中,使用列表推导实际上更可取。当内存不是问题时,它们将优于生成器表达式。
当需要处理大量数据时,就会出现问题,因为列表推导会立即将所有输出的数据存储在内存中(就像我们在代码中看到的那样)。
相反,生成器是一种概念,旨在一次生成一个(产量)结果,而不是将整个数据结构加载到内存中。这样就可以处理庞大的数据集,而不会出现内存使用量激增的风险。
生成器的优点:
1.表情与像功能特别有用sum(),min()和max()。
2.生成器表达式可以轻松地链接(组合)在一起,从而创建一个数据管道,可以逐项处理大量数据。
缺点:不适用于需要多次使用这些值的情况,因为一旦生成器用尽,就无法访问其生成的值。
执行一个小型基准测试,如何将生成器的表达式链接在一起。我们将要进行以下操作:
1)计算文件中每行的长度,
2)然后从每行长度中提取平方根,
3)求平方根。
我使用.txt格式的“夏洛克·福尔摩斯历险记”,你们可以选择其他一些文本文件。
让我们首先使用列表推导:
import time
execution = []
for i in range(100):
start = time.time()
filename = 'Sherlock Holmes.txt'
lengths = [len(line) for line in open(filename)]
roots = [x**0.5 for x in lengths]
print(sum(roots))
end = time.time()
execution.append(end-start)
print(f'Avg execution time with list comprehensions: '
f'{sum(execution)/len(execution):.5f}')
使用生成器表达式,此代码看起来几乎相同:
import time
execution = []
for i in range(100):
start = time.time()
filename = 'Sherlock Holmes.txt'
lengths = (len(line) for line in open(filename))
roots = (x**0.5 for x in lengths)
print(sum(roots))
end = time.time()
execution.append(end-start)
print(f'Avg execution time with generator expressions: '
f'{sum(execution)/len(execution):.5f}')
第二个块将以不同的方式运行。Python不会在每一行上产生整个结果,而是从文件中读取一行,然后测量其长度,然后将其加到总和上。然后,解释器将继续进行下一行,依此类推。这正是将生成器表达式链接(组成)到数据管道中的意思。
因此,让我们运行两个版本:
Avg execution time with list comprehensions: 0.00486
Avg execution time with generator expressions: 0.00530
如您所见,在此数据集上,列表理解的运行速度更快。但是,如果我在包含超过1000万行的文件上运行相同的代码,结果将有所不同(我只是将文本复制粘贴到Sherlock Holmes中多次):
Avg execution time with list comprehensions: 4.26855
Avg execution time with generator expressions: 3.79113
如您所见,在这种情况下,生成器表达式已被理解:
结论
综上所述,生成器表达式在处理大型数据集时效率更高,并且可以帮助您的程序避免崩溃。而且,它们很容易链接在一起,从而创建了能够一一生成结果值的数据管道。
同时,在较小的数据(较小的数据-取决于计算机)上,列表理解通常胜过生成器表达式,如果您需要多次访问生成的数据,则列表理解会更有用。