8

Why does list comprehension have better performance than a for loop, in Python?

list comprehension:

new_items = [a for a in items if a > 10]

for loop:

new_items = []
for a in items:
    if a > 10: new_items.append(a)

Are there other examples (not loops), where one Python structure has worse performance than another Python structure?

7
  • 3
    Is any other example (not loop), when one Python structure has worse performance than other Python structure ? - There are infinitely many. Commented Jun 3, 2013 at 22:53
  • @Lattyware, for example? Commented Jun 3, 2013 at 22:55
  • 6
    You should not worry so much about lc vs loop if performance is the only reason. Worry instead that your overall algorithm is sound and worry that you can understand the code you wrote well. Commented Jun 3, 2013 at 22:56
  • @TomCruise filer(lambda x:x >10, items) Commented Jun 3, 2013 at 22:56
  • @drewk, I know, but in this case performance is my clue. Commented Jun 3, 2013 at 22:58

3 Answers 3

16

Essentially, list comprehension and for loops does pretty similar things, with list comprehension doing away some overheads and making it look pretty. To understand why this is faster, you should look in Efficiency of list comprehensions and to quote the relevant part for your problem:

List comprehensions perform better here because you don’t need to load the append attribute off of the list (loop program, bytecode 28) and call it as a function (loop program, bytecode 38). Instead, in a comprehension, a specialized LIST_APPEND bytecode is generated for a fast append onto the result list (comprehension program, bytecode 33).

In the loop_faster program, you avoid the overhead of the append attribute lookup by hoisting it out of the loop and placing the result in a fastlocal (bytecode 9-12), so it loops more quickly; however, the comprehension uses a specialized LIST_APPEND bytecode instead of incurring the overhead of a function call, so it still trumps.

The link also details some of the possible pitfalls associated with lc and I would recommend you to go through it once.

Sign up to request clarification or add additional context in comments.

5 Comments

also if you wanna time your code you can use %timeit magic funciton in ipython
Also LC doesn't have to worry things like statements, as only expressions are allowed in LCs.
This is not pretty. It's very long statement in first place so I have to split it into multiple lines. Once this is the case, comprehension makes little sense to me: [ [ 1 if item_idx == row_idx else 0 for item_idx in range(0, 3) ] for row_idx in range(0, 3) ], which is example from python-3-patterns-idioms-test.readthedocs.org/en/latest/….
The link seems to be broken :(
just checking back here after a long time. the link is indeed broken. This is the latest url for this from wayback machine @masterforker web.archive.org/web/20190319205826/http://blog.cdleary.com/2010/…
5

Assuming we're talking CPython here, you could use the dis module to compare the generated bytecodes:

>> def one():
       return [a for a in items if a > 10]

>> def two():
       res = []
       for a in items:
           if a > 10:
               res.append(a)

>> dis.dis(one)

  2           0 BUILD_LIST               0
              3 LOAD_GLOBAL              0 (items)
              6 GET_ITER
        >>    7 FOR_ITER                24 (to 34)
             10 STORE_FAST               0 (a)
             13 LOAD_FAST                0 (a)
             16 LOAD_CONST               1 (10)
             19 COMPARE_OP               4 (>)
             22 POP_JUMP_IF_FALSE        7
             25 LOAD_FAST                0 (a)
             28 LIST_APPEND              2
             31 JUMP_ABSOLUTE            7
        >>   34 RETURN_VALUE

>> dis.dis(two)
  2           0 BUILD_LIST               0
              3 STORE_FAST               0 (res)

  3           6 SETUP_LOOP              42 (to 51)
              9 LOAD_GLOBAL              0 (items)
             12 GET_ITER
        >>   13 FOR_ITER                34 (to 50)
             16 STORE_FAST               1 (a)

  4          19 LOAD_FAST                1 (a)
             22 LOAD_CONST               1 (10)
             25 COMPARE_OP               4 (>)
             28 POP_JUMP_IF_FALSE       13

  5          31 LOAD_FAST                0 (res)
             34 LOAD_ATTR                1 (append)
             37 LOAD_FAST                1 (a)
             40 CALL_FUNCTION            1
             43 POP_TOP
             44 JUMP_ABSOLUTE           13
             47 JUMP_ABSOLUTE           13
        >>   50 POP_BLOCK
        >>   51 LOAD_CONST               0 (None)
             54 RETURN_VALUE

So for one thing, the list comprehension takes advantage of the dedicated LIST_APPEND opcode which isn't being used by the for loop.

Comments

2

From the python wiki

The for statement is most commonly used. It loops over the elements of a sequence, assigning each to the loop variable. If the body of your loop is simple, the interpreter overhead of the for loop itself can be a substantial amount of the overhead. This is where the map function is handy. You can think of map as a for moved into C code.

So simple for loops have overhead that list comprehensions get away with.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.