Python技巧:避免大列表理解,生成器表达式往往更有用~

1,231 阅读4分钟

众所周知,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

如您所见,在这种情况下,生成器表达式已被理解:

结论

综上所述,生成器表达式在处理大型数据集时效率更高,并且可以帮助您的程序避免崩溃。而且,它们很容易链接在一起,从而创建了能够一一生成结果值的数据管道。

同时,在较小的数据(较小的数据-取决于计算机)上,列表理解通常胜过生成器表达式,如果您需要多次访问生成的数据,则列表理解会更有用。