The map() function or the list comprehension?

There are two ways to apply a function to each element of an iterated object in python:

  1. the map function:

    >>> lst = [1, 2, 3, 4]
    >>> list(map(str, lst))
    ['1', '2', '3', '4']
    >>> list(map(lambda a: a + 1, lst))
    [2, 3, 4, 5]
    
  2. the list comprehension:

    >>> lst = [1, 2, 3, 4]
    >>> [str(i) for i in lst]
    ['1', '2', '3', '4']
    >>> [i + 1 for i in lst]
    [2, 3, 4, 5]
    

In “The Zen of Python” there is a line:

There should be one-- and preferably only one --obvious way to do it.

Since there are two ways to apply functions to iterated objects, it means there are good reasons for entering them. But which?

So when is it better to use map(), and when is list comprehension?

1 Like

Use a for loop:

lst = [1, 2, 3, 4]
str_lst = []
for i in lst:
    str_lst.append(str(i))

These methods of applying functions to iterated objects have no connection to “There should be one-- and preferably only one --obvious way to do it.”

I think Python has evolved since those early days and many things are no longer that obvious (while others have become more obvious perhaps). For this example, imo it’s purely a question of personal preference or style - or what you happen to find more obvious. The rationale for list comprehensions was:

List comprehensions provide a more concise way to create lists in situations where map() and filter() and/or nested loops would currently be used.

Your code snippet I think does illustrate that with [i + 1 for i in lst] which is definitely more readable than the map + lambda variant. So it’s a more a matter of “Readability counts” :slight_smile:

3 Likes

I will second @hansgeunsmeyer on readability being the biggest thing. On any “serious”[1] project code will be read more often than written, so I try to aim for whatever seems clearest in a particular situation.

Personally I find that I use comprehensions more often, especially for small expressions where I’d need a lambda with map.


  1. Having a team with multiple developers, or a project lifespan long enough that the “other developer” trying to read and understand your code is… future you. :wink: ↩︎

It would be nice if there were only one “obvious way to do it”, in general, but this is almost always a pipe dream. Different people will find different things obvious. This rule is meant more for the language designers than it is for programmers: when there is a common task that needs a few lines of code, and people write the code in different ways, and every way takes a little bit of thought… then this is a suggestion to make the language give some more explicit support. Even if everyone does it the same way, it shouldn’t seem too abstract. Following this principle is how we get nice conveniences such as random.choice.

As for map vs. list comprehensions, this is my personal guidance:

List comprehensions are usually preferred.

Consider map when:

  • the function you want to apply already exists (especially if it is a builtin):

    >>> # Not bad
    >>> [str(x) for x in range(10)]
    ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
    >>> # the map way is eta-reduced, which can be considered an advantage
    >>> list(map(str, range(10)))
    ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
    

    See also about eta-reduction (also called eta-conversion), in a language where people usually care a lot more about this kind of thing :wink:

    Edit: I forgot to mention - when the function is a built-in, the map approach is typically more performant. Or at least, it used to be, the last time I tested it.

  • you can write the transformation easily as a function and that function needs to take multiple arguments from parallel input lists:

    >>> [x*y for x, y in zip(range(10), range(10, 20))]
    [0, 11, 24, 39, 56, 75, 96, 119, 144, 171]
    >>> # the map way has the "zipping" built in already:
    >>> list(map(lambda x, y: x*y, range(10), range(10, 20)))
    [0, 11, 24, 39, 56, 75, 96, 119, 144, 171]
    

Especially prefer a list comprehension when you also need a filter:

>>> # ew
>>> list(map(lambda x: x + 1, filter(lambda x: x % 3, range(10))))
[2, 3, 5, 6, 8, 9]
>>> # much easier
>>> [x + 1 for x in range(10) if x % 3]
[2, 3, 5, 6, 8, 9]

In Python 3.x, map is lazy. If you need or want that lazy result but want to use the list comprehension syntax, write a generator expression instead:

>>> [x for x in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> (x for x in range(10)) # This does not make a tuple.
<generator object <genexpr> at 0x7f1ddd439120>

The resulting “generator object” can be used in all the same basic ways as the map result. (There are technical differences, but they aren’t important here.)

Please don’t write an explicit loop for ordinary cases. Figuring out how to build a list from the results should be Python’s job, not yours. The .append method is not interesting, and the imperative logic to build the list is a distraction from the core idea: i.e., the rule that tells you how the output list elements relate to the input list elements.

2 Likes

Little trip along memory lanes: All Things Pythonic, The fate of reduce() in Python 3000
by Guido van van Rossum
.

I agree with @kknechtel that map should be compared with generator expression as both are lazy. It could be important if large lists must be processed and one needs to do it only once.

I think that taking advantage of Python batteries included approach readability of using map can be in some cases improved as lambda can be avoided:

>>> from itertools import repeat
>>> from operator import add
>>> print(*map(add, [1, 2, 3, 4], repeat(2)))
3 4 5 6
>>> from functools import partial
>>> print(*map(partial(add, 2), [1, 2, 3, 4]))
3 4 5 6
>>> from operator import mul
>>> print(*map(mul, range(10), range(10, 20)))
0 11 24 39 56 75 96 119 144 171
>>> # still ew
>>> print(*map(add, filter(lambda x: x % 3, range(10)), repeat(1)))
2 3 5 6 8 9
>>> print(*map(partial(add, 1), filter(lambda x: x%3, range(10))))
2 3 5 6 8 9