1

Given the following test:

>>> import timeit
>>> timeit.timeit("[x + 'abc' for x in ['x', 'y', 'z']]")
>>> timeit.timeit("map(lambda x: x + 'abc', ['x', 'y', 'z'])")

With Python 2.7 and 3.4 (Debian 8/testing/jessie) I get the following numbers:

Python27 Python34
1.3s     0.5s      map()
0.6s     0.9s      list comprehension

Map improved significantly with Python 3, the list comprehension suffered badly.

Question: When porting code from Python 2 to Python 3, is it recommended to change list comprehensions to map()?

3
  • 2
    map in 3.x doesn't build a list - so the timing here is apples and pears Commented Sep 15, 2014 at 10:27
  • @MartijnPieters the OP here never appears to materialise their map in 3.x... Commented Sep 15, 2014 at 10:28
  • another thing that will change your 'timeit' is using lambdas versus using a name function Commented Sep 15, 2014 at 11:19

1 Answer 1

6

You are not testing correctly. In Python 3, map() returns an iterator, not a list. You are not actually iterating in your test, only testing the creation of the iterator.

You'll need to include iteration to see which approach is faster; you could use collections.deque() with a length of 0, this will iterate without producing a new list object:

import timeit
timeit.timeit("deque([x + 'abc' for x in ['x', 'y', 'z']], maxlen=0)",
              'from collections import deque')
timeit.timeit("deque(map(lambda x: x + 'abc', ['x', 'y', 'z']), maxlen=0)",
              'from collections import deque')

By applying the deque() to both you even out the score again.

Now list comprehensions win on both platforms:

Python27 Python34
1.91s     2.00s      map()
1.18s     1.85s      list comprehension

You should really use far larger input lists to properly test the differences; too much o

The reason list comprehensions slowed down on Python 3 is because they got their own proper scope, just like generator expressions and dict and set comprehensions do on both Python 2 and 3.

If your map function is entirely implemented in C (as opposed to a lambda, which pushes back to Python, map() could win:

>>> timeit.timeit("deque([m(i) for i in ['x', 'y', 'z']], maxlen=0)",
...               "from collections import deque; from operator import methodcaller; m = methodcaller('__add__', 'abc')")
2.3514049489967874
>>> timeit.timeit("deque(map(methodcaller('__add__', 'abc'), ['x', 'y', 'z']), maxlen=0)",
...               'from collections import deque; from operator import methodcaller')
1.7684289459939464

Here the methodcaller() object avoids calling back into Python code by calling the str.__add__ method for each object used.

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

1 Comment

Thanks for the explication, very insightful!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.