Python - 附加 VS 扩展效率

Python - append VS extend efficiency

提问人:Yam Mesicka 提问时间:1/22/2013 最后编辑:Yam Mesicka 更新时间:11/17/2023 访问量:56163

问:

以下是我使用 Python 编写的一些代码:

from math import sqrt
abundant_list = []

for i in range(12,28123+1):
    dividor_list = [1]
    for j in range(2, int(sqrt(i))+1):
        if i%j == 0:
            dividor_list.extend([i/j,j])
    if sum(dividor_list) > i:
        abundant_list.append(i)

print abundant_list

正如你所看到的,代码确实在尽可能地提高效率。

如果我使用两次,还是只使用一次,有什么区别吗? 我知道这可能是微小的差异,但我真的很想知道:)list.appendlist.extend

Python 列表 性能

评论

9赞 NPE 1/22/2013
如果您想知道,请测量。
1赞 mgilson 1/22/2013
如果速度不快于 2,我会感到非常惊讶extend.appends
1赞 Lauritz V. Thaulow 1/22/2013
如果我要对此进行优化,我会使用埃拉托色尼的筛子来找到素数,然后对于每个素数,我会将其分解并使用 itertools.product 来获得将因子组合成除数的所有方法,并最终将它们相加。sqrt(28123)i

答:

26赞 mgilson 1/22/2013 #1
import timeit

def append2x(foo):
    foo.append(1)
    foo.append(1)

def extend_lst(foo):
    foo.extend([1,1])

def extend_tup(foo):
    foo.extend((1,1))


l1 = []
l2 = []
l3 = []

print timeit.timeit('append2x(l1)',setup = 'from __main__ import append2x,l1')
print timeit.timeit('extend_lst(l2)',setup = 'from __main__ import extend_lst,l2')
print timeit.timeit('extend_tup(l3)',setup = 'from __main__ import extend_tup,l3')

这是一个简单的基准测试。我的结果(os-X、10.5.8、core2duo、FWIW):

0.520906925201  #append
0.602569103241  #extend-list
0.357008934021  #extend-tuple

结果的顺序与我的 linux 盒子(Ubuntu、x86-64 core i7)相同:

0.307395935059  #append
0.319436073303  #extend-list
0.238317012787  #extend-tuple

对我来说,这比 更快,但与创建一个相比,创建一个相对昂贵extendappendlisttuple


编辑

在下面的评论中指出,由于元组的不可变性,解释器可以优化元组的创建(它创建一次元组并一遍又一遍地重用它)。如果我们将代码更改为:

def extend_lst(foo):  
    v = 1
    foo.extend([v,v]) 

def extend_tup(foo):
    v = 1
    foo.extend((v,v))

时间几乎相同:

0.297003984451  #append
0.344678163528  #extend-list
0.292304992676  #extend-tuple

尽管仍然始终如一地击败列表版本,并且在我所做的所有试验中几乎没有超过该版本。tupleappend

我从中得出的结论是,如果你要遍历一个由所有文字组成的对象,请选择 a 而不是 .如果它不完全由文字组成,那么无论你选择还是 .tuplelistlisttuple

评论

0赞 Yam Mesicka 1/22/2013
好吧,刚刚学习了“time.clock”函数,令人惊讶的是,它表明扩展要慢得多......将编辑我的消息
5赞 1/22/2013
元组版本速度更快,因为您使用的元组是文本,因此可以重复使用(参见字节码),因此不必一次又一次地构造它。使用变量(例如,传递一个对象以附加到所有函数),差异将减少。
1赞 Yam Mesicka 1/22/2013
很棒的答案,谢谢。总是值得提出新问题,总是学习新东西:)
0赞 mgilson 1/22/2013
@delnan -- 大鱼!虽然,在重复运行几次后仍然将其边缘化。tuple
2赞 mgilson 1/22/2013
@delnan -- 我正在研究它。我添加了一个解释这些东西的编辑。我通常不会在答案中留下糟糕的时间,但在这种情况下,我觉得让两个版本都留下来展示文字元组比文字列表快得多是有启发性的,但是当使用变量时,它们相对接近。
0赞 ATOzTOA 1/22/2013 #2

它们需要完全相同的时间。

以下是代码所花费的时间:

dividor_list.extend([i/j,j])

>>> 
0:00:00.410000
>>> 
0:00:00.383000
>>> 
0:00:00.389000

dividor_list.append(i/j); dividor_list.append(j)

>>> 
0:00:00.400000
>>> 
0:00:00.390000
>>> 
0:00:00.381000

评论

5赞 1/22/2013
你必须包括你的计时代码,因为这很容易搞砸。而且由于您显然没有使用 ,因此您必须证明不使用timeittimeit ;-)
0赞 Steven Rumbalski 1/22/2013
McGilson 的基准测试建议比两者都更有效,因为它不会创建中间列表。创建中间元组更便宜。dividor_list.extend((i/j,j))
0赞 1/22/2013
@StevenRumbalski 不,它只是表明 one 比 2 到 3 秒、其他一些操作和 a 快LOAD_CONSTLOAD_FASTBUILD_LIST ;-)
1赞 Steven Rumbalski 1/22/2013
@delnan。采取的点。使用您建议的修改,元组版本在我的系统上的时钟仍然更快一些。但是,改进太少,无法真正关心使用哪个。
24赞 Marc Schulder 12/4/2015 #3

编辑:正如其他人在评论中指出的那样,Python 中没有元组推导,因为该语法已用于生成器表达式。我已经澄清了描述。

还值得指出的是,这个问题的答案取决于每次迭代中添加的列表/元组的大小。对于较大的列表,extend 显然更胜一筹(列表与元组没有区别)。从 mgilson 的回答开始,我检查了包含 600 个项目而不是 2 个项目的集合的行为: 调用 append 600 次所需的时间是使用手动定义的列表/元组的 8 倍(即):extend()[v,v,v,v,v,v,v...]

42.4969689846
5.45146393776
5.38034892082

这五秒钟的大部分实际上是列表/元组的创建。在通话前做好准备,可以缩短时间到timeit

1.42491698265
0.657584905624

分别用于 list 和 tuple。

对于更真实(和更公平)的情况,可以在函数调用中动态生成数据:

import timeit

def append_loop(foo, reps):
    for i in range(reps):
        foo.append(i)

def append_comp(foo, reps):
    [foo.append(i) for i in range(reps)]

def extend_lst(foo, reps):
    foo.extend([i for i in range(reps)])

def extend_genexp(foo, reps):
    foo.extend((i for i in range(reps)))

repetitions = 600

print timeit.timeit('append_loop([], repetitions)', setup='from __main__ import append_loop, repetitions')
print timeit.timeit('append_comp([], repetitions)', setup='from __main__ import append_comp, repetitions')
print timeit.timeit('extend_lst([], repetitions)', setup='from __main__ import extend_lst, repetitions')
print timeit.timeit('extend_genexp([], repetitions)', setup='from __main__ import extend_genexp, repetitions')

(Append 通过 for 循环和列表推导实现,以分解两种循环方式之间的效率差异。

时间安排如下:

53.8211231232
57.1711571217
19.8829259872
28.5986201763

正如我们所看到的,使用列表推导进行扩展仍然比追加快两倍以上。生成器表达式的显示速度明显慢于列表推导。 只会引入不必要的列表创建开销。append_comp

评论

4赞 yoch 11/13/2017
后面的 () 实际上是一个 genexp 而不是一个元组,这解释了速度慢的原因。extend_tup
1赞 Marc Schulder 11/14/2017
你对输出类型的看法是对的,我的错误。但是,我刚刚测试了元组理解,它提供了与 genexp 相同的速度。列表理解速度仍然更快。显然,如果元组是预先计算的,则调用会更快,但对于预先计算的列表也是如此。
1赞 yoch 12/5/2017
你的基准测试是有偏见的,因为它包括构建列表和元组所需的时间。没有它,用元组扩展列表会更快一些(至少对我来说,使用 python 3.5)。
0赞 Zvi 12/26/2020
您的测试具有误导性,正确的测试表明追加是最快的方法。您使用的两个追加测试一次都附加一个项目,但 extend() 适用于整个列表。当我更改append_comp的代码时,使追加不在理解列表中(如在最后 2 个测试中),最后 3 个测试的时间(在我使用 Jupyter 实验室解释器在我的慢速机器上的测试中):91、104、148 秒恭敬。
1赞 Paras Gupta 3/6/2023
解释得很好,我想纠正你,这是一个生成器,而不是一个元组(i for i in range(reps))
0赞 Markus Dutschke 7/19/2023 #4

只是另一个基准

这次是在 iPython 中。如果您懒得创建新的 python 脚本,可能会很有用。

In [12]: %%time
    ...: l = []
    ...: for ii in range(10000):
    ...:     l += [ii]
    ...: 
CPU times: user 886 µs, sys: 0 ns, total: 886 µs
Wall time: 889 µs

In [13]: %%time
    ...: l = []
    ...: for ii in range(10000):
    ...:     l.append(ii)
    ...: 
CPU times: user 669 µs, sys: 92 µs, total: 761 µs
Wall time: 763 µs