【问题标题】:generator vs list comprehension生成器与列表理解
【发布时间】:2013-12-30 19:48:58
【问题描述】:

在下面的代码中,使用列表解析还是生成器更好?

from itertools import izip
n=2
l=izip(xrange(10**n), xrange(10**n))
print 3 not in [x[0] for x in l]
#or
#print 3 not in (x[0] for x in l)

在这些测试中,如果列表很大,生成器会更快,如果列表更短,则列表理解显然更快。
这是因为理解是计算机一次吗?
对于大型列表:generatorlistcomp
对于小列表:generatorlistcomp

【问题讨论】:

标签: python generator list-comprehension


【解决方案1】:

in 针对生成器表达式将使用__iter__() 方法并迭代表达式直到找到匹配项,使其在一般情况下比列表推导更有效,后者在扫描匹配结果之前首先生成整个列表。

您的具体示例的替代方法是使用any(),以使测试更加明确。我觉得这更具可读性:

any(x[0] == 3 for x in l)

您必须考虑到in 确实转发了生成器;如果您还需要在其他地方使用生成器,则不能使用此方法。

至于你的具体时间测试;您的“简短”测试存在致命缺陷。 izip() 生成器的第一次迭代将完全耗尽,使其他 9999 次迭代针对 empty 生成器进行测试。您正在测试在那里创建一个空列表和一个空生成器之间的区别,从而放大了创建成本的差异。

此外,您应该使用timeit module 运行测试,确保测试可重复。这意味着您也必须在每次迭代时创建一个新的 izip() 对象;现在对比度要大得多

>>> # Python 2, 'short'
...
>>> timeit.timeit("l = izip(xrange(10**2), xrange(10**2)); 3 not in (x[0] for x in l)", 'from itertools import izip', number=100000)
0.27606701850891113
>>> timeit.timeit("l = izip(xrange(10**2), xrange(10**2)); 3 not in [x[0] for x in l]", 'from itertools import izip', number=100000)
1.7422130107879639
>>> # Python 2, 'long'
...
>>> timeit.timeit("l = izip(xrange(10**3), xrange(10**3)); 3 not in (x[0] for x in l)", 'from itertools import izip', number=100000)
0.3002200126647949
>>> timeit.timeit("l = izip(xrange(10**3), xrange(10**3)); 3 not in [x[0] for x in l]", 'from itertools import izip', number=100000)
15.624258995056152

在 Python 3 上:

>>> # Python 3, 'short'
... 
>>> timeit.timeit("l = zip(range(10**2), range(10**2)); 3 not in (x[0] for x in l)", number=100000)
0.2624585109297186
>>> timeit.timeit("l = zip(range(10**2), range(10**2)); 3 not in [x[0] for x in l]", number=100000)
1.5555254180217162
>>> # Python 3, 'long'
... 
>>> timeit.timeit("l = zip(range(10**3), range(10**3)); 3 not in (x[0] for x in l)", number=100000)
0.27222433499991894
>>> timeit.timeit("l = zip(range(10**3), range(10**3)); 3 not in [x[0] for x in l]", number=100000)
15.76974998600781

在所有情况下,生成器变体都快得多;您必须将“短”版本缩短为仅 8 个元组,列表理解才能开始获胜:

>>> timeit.timeit("n = 8; l = izip(xrange(n), xrange(n)); 3 not in (x[0] for x in l)", 'from itertools import izip', number=100000)
0.2870941162109375
>>> timeit.timeit("n = 8; l = izip(xrange(n), xrange(n)); 3 not in [x[0] for x in l]", 'from itertools import izip', number=100000)
0.28503894805908203

在 Python 3 上,生成器表达式和列表推导的实现更加接近,在列表推导获胜之前,您必须减少到 4 项:

>>> timeit.timeit("n = 4; l = zip(range(n), range(8)); 3 not in (x[0] for x in l)", number=100000)
0.284480107948184
>>> timeit.timeit("n = 4; l = zip(range(n), range(8)); 3 not in [x[0] for x in l]", number=100000)
0.23570425796788186

【讨论】:

  • 我会使用 for 循环,这样它就不会构建整个列表,但看起来很难看
  • 您可以将in 与生成器表达式一起使用。它只会消耗生成器直到第一次匹配。
  • @gnibbler: 好吧,所以你可以; in 将使用 __iter__() 进行迭代,直到有匹配为止..
  • 当然 - in 的行为与 Python 3.x 的 range 不同,因为它有一个自定义 __contains__,它在数学上计算成员资格而不是具体化范围......这意味着上述测试可能完全不同......
【解决方案2】:

创建生成器表达式有相当多的开销,但最终您不需要分配大量内存来弥补它。

小型列表推导式更快,因为它们没有那么大的开销。

通常情况下小的情况足够接近,所以在这种情况下最好选择生成器表达式

在可能同时存在 100 或 1000 个连接的网络服务器上节省内存尤为重要。

【讨论】:

    【解决方案3】:

    创建生成器比创建列表要慢,因此您必须考虑变量:创建对象的时间和测试表达式的时间。所以回答你的问题,如果“更好”是指“更快”:这取决于n

    【讨论】:

      猜你喜欢
      • 2016-10-04
      • 2023-02-02
      • 1970-01-01
      • 2017-08-05
      • 2013-08-15
      • 2015-07-18
      • 2021-04-07
      • 2016-07-09
      相关资源
      最近更新 更多