【问题标题】:Why is list.append(x) more efficient than list += l[x]? [duplicate]为什么 list.append(x) 比 list += l[x] 更有效? [复制]
【发布时间】:2016-03-21 07:32:25
【问题描述】:

追加到列表可能有两种方式:

1)

mat = []
for i in range(10):
    mat.append(i)

2)

mat = []
for i in range(10):
    mat += [i]

来自official Python documentation

示例中显示的方法 append() 是为列表对象定义的;它在列表末尾添加一个新元素。在本例中,它等价于 result = result + [a],但效率更高。

文档表明方法 1 更有效。为什么会这样?

【问题讨论】:

  • 但我建议mat = [i for i in range(10)]
  • 是的。这也是正确的。那么在可能的方法中,哪种方法更有效?
  • 或者甚至只是mat = list(range(10))。列表推导通常优于显式循环,但某些情况(复杂的过滤,或一次构建多个列表)更适合循环。
  • 我认为这主要是基于意见的。虽然我更喜欢 .append 对我来说更具可读性
  • @Altoyr 不。这不是 POB。如果问题是我应该使用这个还是这个,那么它会是。但这是在问为什么这比那更快。我希望你明白了:-)

标签: python list


【解决方案1】:

尽管使用 .append 需要一个方法调用,但它实际上比使用扩展赋值运算符 += 更有效。

但是使用.append 有一个更好的理由:当您附加到的列表不在本地范围内时,您可以这样做,因为它只是在外部范围内调用对象的方法,而除非您将它们声明为全局(或非局部),否则您不能对不在本地范围内的对象执行赋值,这种做法通常最好避免。

这是一个例子:

mat = []

def test_append():
    for i in range(10):
        #mat += [i]
        mat.append(i)

def test_iadd():
    for i in range(10):
        mat += [i]

test_append()
print(mat)
test_iadd()
print(mat)

输出

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Traceback (most recent call last):
  File "./qtest.py", line 29, in <module>
    test_iadd()
  File "./qtest.py", line 25, in test_iadd
    mat += [i]
UnboundLocalError: local variable 'mat' referenced before assignment

当然,我们可以将mat 作为参数传递给函数:

mat = []

def test_append():
    for i in range(10):
        #mat += [i]
        mat.append(i)

def test_iadd2(mat):
    for i in range(10):
        mat += [i]

test_append()
print(mat)
test_iadd2(mat)
print(mat)

输出

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

test_iadd2 较慢的一个原因是它必须在 every 循环上构造一个新的[i] 列表。

FWIW,这是字节码:

test_append
 21           0 SETUP_LOOP              33 (to 36)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (10)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                19 (to 35)
             16 STORE_FAST               0 (i)

 23          19 LOAD_GLOBAL              1 (mat)
             22 LOAD_ATTR                2 (append)
             25 LOAD_FAST                0 (i)
             28 CALL_FUNCTION            1
             31 POP_TOP             
             32 JUMP_ABSOLUTE           13
        >>   35 POP_BLOCK           
        >>   36 LOAD_CONST               0 (None)
             39 RETURN_VALUE        

test_iadd2
 26           0 SETUP_LOOP              33 (to 36)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (10)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                19 (to 35)
             16 STORE_FAST               1 (i)

 27          19 LOAD_FAST                0 (mat)
             22 LOAD_FAST                1 (i)
             25 BUILD_LIST               1
             28 INPLACE_ADD         
             29 STORE_FAST               0 (mat)
             32 JUMP_ABSOLUTE           13
        >>   35 POP_BLOCK           
        >>   36 LOAD_CONST               0 (None)
             39 RETURN_VALUE        

上述字节码转储是使用dis 模块生成的:

from dis import dis

mat = []

def test_append():
    for i in range(10):
        #mat += [i]
        mat.append(i)

def test_iadd2(mat):
    for i in range(10):
        mat += [i]

print('test_append')
dis(test_append)

print('\ntest_iadd2')
dis(test_iadd2)

【讨论】:

  • "尽管使用 .append 需要一个方法调用,但它实际上比使用增强赋值运算符 += 更有效。"问题是为什么append+= 更有效率。提供支持这一主张的来源。
  • @VincentSavard:公平的评论,虽然我的回答的主要重点是,除了效率还有另一个原因,为什么首选使用.append。但我希望我的答案的补充在一定程度上解决了您的担忧。
  • 确实如此,我认为在问题的上下文中这是一个更好的答案。我投了你一票。
  • @PM2Ring,如何获取字节码?
  • @mrvol:对不起,我会将该信息添加到我的答案中。
【解决方案2】:


import timeit
timeit.timeit('for i in range(10): mat.append(i)', 'mat = []')
1.798893928527832
timeit.timeit('for i in range(10): mat += [i]', 'mat = []')
2.547478199005127

方法append 更快,因为它不使用任何类型转换。

【讨论】:

  • += 也不使用任何类型转换。
  • 你不应该颠倒timeit调用的两个语句吗?首先创建一个空列表,然后将元素附加到它...
  • @pacholik mat += [i] 相当于 mat.extend(list(i))。您可以检查它以尝试运行mat += i
  • @toti08 不,不是。 timeit 创建一个新的空范围,由第二条语句 'mat = []' 定义。如果我理解正确的话……
  • @mrvol 那里面的类型转换在哪里?
【解决方案3】:

第二个比较慢,因为它创建了一个新列表(操作有点慢)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-08-29
    • 2014-05-19
    • 1970-01-01
    • 2012-02-20
    • 2018-09-25
    • 1970-01-01
    • 2019-02-09
    • 2021-02-02
    相关资源
    最近更新 更多