Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is list.append(x) more efficient than list += l[x]? [duplicate]

Tags:

python

list

Appending to list can be possibly done in two ways:

1)

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

or

2)

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

From the official Python documentation:

The method append() shown in the example is defined for list objects; it adds a new element at the end of the list. In this example it is equivalent to result = result + [a], but more efficient.

The documentation suggests that approach 1 is more efficient. Why is it so?

like image 961
prashanth Avatar asked Dec 15 '15 13:12

prashanth


2 Answers

Even though using .append requires a method call it's actually slightly more efficient than using the augmented assignment operator, +=.

But there's an even better reason to use .append: you can do it when the list you're appending to isn't in the local scope, since it's just calling the method of an object in an outer scope, whereas you cannot perform assignments on objects that aren't in the local scope unless you declare them as global (or nonlocal), a practice which is generally best to avoid.

Here's an example:

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)

output

[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

Of course, we can pass mat as an argument to the function:

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)

output

[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]

One reason that test_iadd2 is slower is that it has to construct a new [i] list on every loop.

FWIW, here's the bytecode:

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        

The above bytecode dump was produced using the dis module:

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)
like image 58
PM 2Ring Avatar answered Sep 27 '22 21:09

PM 2Ring



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

method append is faster, because it does't use any type conversion.

like image 36
mrvol Avatar answered Sep 27 '22 22:09

mrvol