5

I have a list of objects with a method, say cleanup(), that I need to invoke on all of them. I understand there are at least two ways:

map(lambda x: x.cleanup(), _my_list_of_objects)

or

for x in _my_list_of_objects:
    x.cleanup()

My question(s):

  1. Is one more Pythonic than the other?
  2. Are there other better ways? I see the former being used often in the code base (at work) here, but it bugs me that map constructs and returns a list that is then thrown away (right?).
  3. Is the perf impact (of a list that is immediately GC'd) something to consider?
scorpiodawg
  • 5,612
  • 3
  • 42
  • 62
  • Guido apparently doesn't like functional programming, so most anything you use "map" for, you'd probably want to do in a list comprehension to remain "pythonic". That being said, what does cleanup() do? If it's mutating values in place, you _definitely_ don't want to use `map` or a list comprehension. – KChaloux Oct 09 '13 at 20:23
  • I guess I mean "idiomatic Python." I've seen the phrase "Pythonic" bandied about a lot, but I'm new to Python so I'm not even sure when it's Pythonic to use the phrase "Pythonic". – scorpiodawg Oct 09 '13 at 20:24
  • You can also use `[x.cleanup() for x in _my_list_of_objects]` this will return you a list with the results of cleanup() – solarc Oct 09 '13 at 20:25
  • 1
    Related (though about list comprehensions rather than `map`): http://stackoverflow.com/questions/5753597/is-it-pythonic-to-use-list-comprehensions-for-just-side-effects – David Robinson Oct 09 '13 at 20:25
  • @KChaloux: I think for this question you can assume cleanup() is a method on each object that does something to the object but not the list itself. Also, I'm not sure how I'd use a list comprehension here? – scorpiodawg Oct 09 '13 at 20:26
  • @DavidRobinson I think that question is very much related, thanks for the link. – scorpiodawg Oct 09 '13 at 20:34

3 Answers3

7

It is not idiomatic to use map, list comprehensions, generator expressions - or anything else that applies some operations on n values and collects the results - for side effects. Even if you insisted on using functional programming concepts where they don't apply (namely in impure code), there are practical concerns:

  • It wastes memory and computation accumulating n references (most likely to None) that will be thrown away immediately afterwards.
  • Python 3 map is lazy, so this code silently stops working if you port to Python 3 (especially if you avoid 2to3 - there are good reasons to).
  • It's most likely slower, not only due to point 1. map has to perform a full function call for every element, the explicit loop only needs a single jump at bytecode level. The list comprehension doesn't need that AFAICT, but it's still measurably slower in my brief experiments with timeit.

If you want to do something for every element of an iterable, that's exactly what the for loop is for. Plain and simple. You won't get any more readable, and although in some cases more obscure variants might be faster on certain Python implementations, it's virtually never worth the expense in readability (and the time to verify that it's actually a win!).

Community
  • 1
  • 1
  • +1 for being so synchronized with me in both time and contents of the answer :) – Bakuriu Oct 09 '13 at 20:38
  • being on python 2.6, didn't know map would fail on python 3, or that it was intended only for cases where you returned values. used it because thought it was faster, but actually doesn't appear to be the case usually.... – Corley Brigman Oct 10 '13 at 03:58
6
  1. The for-loop solution is better simply because you don't care for the result of cleanup. map is part of the functional subset of python, using it to create side-effects is a bad way of using it.

    Also note that map wastes space in python2(since it creates a list) and doesn't work in python3 where you have to consume the iterable. In python3 you'd have something like:

    from collections import deque
    deque(map(function, iterable), maxlen=0)
    

    in order to call the functions without space overhead, and I believe this looses much of the for-loop readability. The for-loop solution works on any python version.

  2. There are many ways to do that, but none more readable then the for-loop and none that provides significant performances benefits over it.

  3. It depends on the size of the list and the specific situation. If the code is in a tight loop then it may make a significant impact. However python is highly optimized to handle small objects, hence if the number of elements is small no memory allocation is actually performed by the OS.

Bakuriu
  • 98,325
  • 22
  • 197
  • 231
0

so, i have been using map() as shorthand for 'call this function on a bunch of objects in a list'. after all, list comprehensions are (usually) faster than doing the same thing in a loop, so why wouldn't map?

anyways, here is the result:

r = [[] for _ in range(10000)]
%timeit map(lambda y: r[y].append(1), x)
100 loops, best of 3: 2.56 ms per loop

r = [[] for _ in range(10000)]
%%timeit
for y in x:
    r[y].append(1)
1000 loops, best of 3: 1.67 ms per loop

for this case, not even close - the for loop method is about twice as fast.

Corley Brigman
  • 11,633
  • 5
  • 33
  • 40