与列表推导相比,生成器表达式的工作量更少吗?

时间:2015-02-24 05:41:31

标签: python optimization lazy-loading list-comprehension generator-expression

在重构一段代码时,我注意到了这一点:

if product_id in [c["id"] for c in self.data.load_products()]:
    # Do something

反过来,load_products()执行SQL查询,并为每个产品执行:

  • 执行一些相对CPU成本高昂的操作,并且:
  • 使用yield将产品逐个返回给来电者。

据我所知,列表理解和生成器表达式之间的区别在于,在列表理解的情况下,所有产品都将从数据库加载并处理,即使第一个产品是匹配的。

因此,如果我用这样的生成器表达式替换它:

#                ↴                                          ↴
if product_id in (c["id"] for c in self.data.load_products()):
    # Do something

它可以通过最终减少工作来改进代码,即一旦找到匹配,下一个产品将不会从数据库加载,也不会被处理。

尽管如此,我还是不太了解Python。

我是对的吗? Python会在找到匹配项后立即停止,或者两段代码都会执行相同操作并从数据库加载每个产品吗?

2 个答案:

答案 0 :(得分:2)

列表理解将始终运行到最后并将所有结果保存在内存中。

生成器(表达与否)可以短路(如果使用得当) - 例如if product_id in <some generator>确实会在找到匹配后立即停止,只有在那里运行到最后不配。

答案 1 :(得分:1)

List Comprehension的主要目的是创建一个新列表。因此,只有当它运行并准备一个新列表时才会停止。如果列表中的任何项与正在搜索的实际项匹配,in运算符将迭代新生成的列表并返回True。例如,

>>> lc = [item * 2 for item in range(10)]
>>> lc
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> 4 in lc
True

如果是生成器表达式,in运算符将调用生成器的__iter__方法,当__iter__调用返回的项与实际项匹配时,它将立即停止搜索。您可以像这样确认

>>> ge = (item * 2 for item in range(10))
>>> ge
<generator object <genexpr> at 0x7f498a85fd70>
>>> 4 in ge
True
>>> list(ge)
[6, 8, 10, 12, 14, 16, 18]

如您所见,ge仅在找到匹配项之前进行迭代。在ge检查后转换为列表时,4 in ge对象会给出生成器表达式生成的其余元素。

因此,在这种情况下,Generator Expressions更好。