DEV Community

Muhammed H. Alkan
Muhammed H. Alkan

Posted on

Map vs List comprehension in Python

So, I was trying to learn Elm (I just gave up), then I saw this feature request:

Request: add list comprehensions #147

I suggest that list comprehensions be added to Elm. They see heavy use in languages that implement them. Considering the existing similarities between Elm and Haskell syntax, I propose using Haskell style list comprehensions, like this:

[(x, y) | x <- [1..10], y <- [1..20], x /= y]

I was trying to find an alternative to list comprehension in Elm, they was recommending to use map. List comprehension is just a Syntactic Sugar. Then let's compare both of them in Python.

Performance

map performance:

lambdef lab λ python -m timeit "map(lambda a: a*a, range(100))"
100000 loops, best of 3: 11.5 usec per loop

List comprehension performance:

lambdef lab λ python -m timeit "[a*a for a in range(100)]"     
100000 loops, best of 3: 5.51 usec per loop

List comprehension is 2x faster!

Syntactical comparison

Actually, List comprehension looks more concise and beautiful than map.
For example:

[n for n in nlist]|
                  |
                  |
map(lambda n: n, nlist)

It's 5 bytes larger and slower! Bad!

Bytecode generation

There is dis to show Python Bytecode of Python code. Let's see the bytecode of list comprehension and map to compare the performance.

Bytecode of [n for n in range(100)] (List comprehension)

  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x7f042c8eac00, file "<dis>", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (range)
              8 LOAD_CONST               2 (100)
             10 CALL_FUNCTION            1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 RETURN_VALUE

It loads list comprehension, creates function, loads range function and loads 100 to give it as argument, calls range function, and returns the value.

Bytecode of map(lambda n: n, range(100)) (Map)

  1           0 LOAD_NAME                0 (map)
              2 LOAD_CONST               0 (<code object <lambda> at 0x7f042c8ead20, file "<dis>", line 1>)
              4 LOAD_CONST               1 ('<lambda>')
              6 MAKE_FUNCTION            0
              8 LOAD_NAME                1 (range)
             10 LOAD_CONST               2 (100)
             12 CALL_FUNCTION            1
             14 CALL_FUNCTION            2
             16 RETURN_VALUE

It loads map function, it loads the lambda (lambda n: n), makes the lambda function object, loads range function and loads 100 to give it as argument, calls two functions and returns the value.

In my opinion, Python Bytecode is understandable by humans™.

Pros & Cons

List comprehension

  • Pros
    • Support for generating lists directly
    • Support for generating generators directly
    • Filtering (ifs)
    • Concise
    • Fast
  • Cons
    • No cons! (IMO)

Map

  • Pros
    • No pros! (IMO)
  • Cons
    • Generates only map object (You need to turn it into the object you want later)
    • No ifs (filtering)
    • Slower
    • Verbose

Code examples

  • Double each number to 100
# List comprehension
[n*2 for n in range(100)]

# Map
map(lambda n: n*2, range(100))
  • Apply fn to each number to 100
# List comprehension
[fn(n) for n in range(100)]

# Map
map(fn, range(100))
  • Filter evens
# List comprehension
[n for n in range(100) if n % 2 == 0]

# Map
not_possible()
# But you can use filter:
# filter(lambda n: n % 2 == 0)

Conclusion

Use list comprehension over map while optimizing your code.

Thanks for reading!

Oldest comments (4)

Collapse
 
lyfolos profile image
Muhammed H. Alkan

It's not :)

Collapse
 
grayjack profile image
GrayJack

Well, I agree with you when talking about python, but in the end, it depends how each language was implemented.

R for example have way faster functional style code than the same in imperative style.

Rust have similar speeds with both functional and imperative styles. Some data types may have drawbacks in one of the 2 styles, but all primitive types and most of the std data structures don't.

I fell that deep down in python implementation, list comprehension are implemented in what python is better at (or more optimized at), imperative paradigm.

I fun test could be R list comprehension vs R map, plus comparing with python and looking at their implementations deep down in the language.

I may do that at some point in my life. XD

Collapse
 
fractionalray profile image
Ray

I just have a quick question. What version of python are you using? The reason I am asking is due to our own experiments with map vs list comprehensions differ based on python2 and python3.

Thanks

Collapse
 
alanmarazzi profile image
Alan

The timing in the example above are wrong. The map example generates a new function at every loop, while the list comprehension just multiplies the result.

If you define a function outside or use one already available, map should actually be slightly faster (without taking into account that map is lazy, so if you don't need that value you'll have the best performance possible :))