Python列表理解与生成器


问题内容

在Python中发现了这个问题Generators vs List
Comprehension的性能,
我使用timeit代替了cProfile。

from timeit import timeit
import cProfile

print timeit('sum([i for i in range(9999999)])', number=1)
print timeit('sum((i for i in range(9999999)))', number=1)

print cProfile.run('sum([i for i in xrange(9999999)])')
print cProfile.run('sum((i for i in xrange(9999999)))')

结果是

LC timeit 0.728941202164
G timeit 0.643975019455
LC cProfile          3 function calls in 0.751 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.673    0.673    0.751    0.751 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.078    0.078    0.078    0.078 {sum}


None
G cProfile          10000003 function calls in 1.644 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 10000000    0.843    0.000    0.843    0.000 <string>:1(<genexpr>)
        1    0.000    0.000    1.644    1.644 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.801    0.801    1.644    1.644 {sum}

我相信生成器应该比列表理解更好,但是为什么在这种情况下结果尚不清楚。我的问题是哪个更好写

sum((i for i in list_of_i))   # Which use 1 loop

sum([i for i in list_of_i])   # Which seem to took 2 loop: 1 for list create and one for sum

问题答案:

在简单的情况下,如果没有理解/生成器,这样做最快的方法是:

sum(xrange(9999999))

通常,如果需要执行某种操作,需要在理解和生成器表达式之间进行选择,则可以执行以下操作:

sum(a*b for a, b in zip(c, d))

就我个人而言,我认为生成器表达式(没有多余的括号1) 看起来 更好,并且由于可读性很重要-这超过了两个表达式之间的任何微性能差异。

生成器通常会因为这种情况而变得更快,因为它们避免创建中间列表(以及与之关联的内存分配)。随着内存分配和列表调整大小需要花费更多时间来处理更大的列表,随着列表变大,时序差异可能会更加明显。但是,情况并非总是如此(在StackOverflow上有很好的记录,str.join与CPython中str.join的生成器相比,使用List进行列表处理的速度更快,因为当获取生成器时,无论如何它都会构造列表…)。

1每当将生成器表达式作为唯一参数传递给函数时,都可以省略括号-这比您预期的发生得更多…