11

I have a list of objects and they have a method called process. In Python 2 one could do this

map(lambda x: x.process, my_object_list)

In Python 3 this will not work because map doesn't call the function until the iterable is traversed. One could do this:

list(map(lambda x: x.process(), my_object_list))

But then you waste memory with a throwaway list (an issue if the list is big). I could also use a 2-line explicit loop. But this pattern is so common for me that I don't want to, or think I should need to, write a loop every time.

Is there a more idiomatic way to do this in Python 3?

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Neil
  • 3,020
  • 4
  • 25
  • 48
  • 1
    FYI, you don't *really* need the lambda. Assuming `x`'s type is `X`, then `map(X.process, list_of_objects)` will work just fine – DeepSpace Jul 25 '18 at 13:12
  • 1
    @DeepSpace Making assumptions is usually bad, and supporting polymorphism is usually good (: – Aran-Fey Jul 25 '18 at 13:13
  • 1
    If you don't want to write two lines of code every time, stuff those 2 lines of code into a function. That's what functions are for. – Aran-Fey Jul 25 '18 at 13:16
  • 1
    highly related https://stackoverflow.com/questions/5753597/is-it-pythonic-to-use-list-comprehensions-for-just-side-effects – Chris_Rands Jul 25 '18 at 13:16
  • 1
    `"But this pattern is so common for me that I don't want to, or think I should need to, write a loop every time."` Well, that's what functions are for – DeepSpace Jul 25 '18 at 13:16
  • 3
    `"In Python 3... map doesn't call the function until the iterable is traversed."` Followed by: `"But then you waste memory with a throwaway list..."` Just want to make sure you do understand that in Python 2, the thrown away `list` is still the case. The switch to an iterator in 3 is a great improvement. At any rate: just use a for loop. There is no superior idiom. – Rick Jul 25 '18 at 13:27
  • @RickTeachey I suspected that that might be the case but I wasn't sure. Thanks for clarifying. – Neil Jul 25 '18 at 13:30
  • 1
    Only use `map` if you intend to *delay*- or partially use- the iterator it creates. If you intend to use all of it immediately, just go ahead and run the code. The `map` is superfluous. – Rick Jul 25 '18 at 13:31

2 Answers2

10

Don't use map or a list comprehension where simple for loop will do:

for x in list_of_objs:
    x.process()

It's not significantly longer than any function you might use to abstract it, but it is significantly clearer.

Of course, if process returns a useful value, then by all means, use a list comprehension.

results = [x.process() for x in list_of_objs]

or map:

results = list(map(lambda x: x.process(), list_of_objs))

There is a function available that makes map a little less clunky, especially if you would reuse the caller:

from operator import methodcaller
processor = methodcaller('process')
results = list(map(processor, list_of_objs))
more_results = list(map(processor, another_list_of_objs))

If you are looking for a good name for a function to wrap the loop, Haskell has a nice convention: a function name ending with an underscore discards its "return value". (Actually, it discards the result of a monadic action, but I'd rather ignore that distinction for the purposes of this answer.)

def map_(f, *args):
    for f_args in zip(*args):
        f(*f_args)

# Compare:
map(f, [1,2,3])  # -- return value of [f(1), f(2), f(3)] is ignored
map_(f, [1,2,3])  # list of return values is never built
chepner
  • 497,756
  • 71
  • 530
  • 681
  • I'm not sure but I think this misses the idea behind OPs question. It is evident that they simply want to consume the generator that `map` returns without: **1.** creating a list, either by a list comprehension or by `list(map(...))` **2.** writing an "explicit" loop – DeepSpace Jul 25 '18 at 13:24
  • 3
    Why use `map` to build an iterator if you don't want an iterator? The loop is the idiomatic, Pythonic approach to calling a function immediately on a list of objects. – chepner Jul 25 '18 at 13:27
  • It's interesting that you use Haskell as an inspiration. On reflection, I think the conceptual reason that I was having difficulty is because map is very 'functional', and I was trying to get it to play nice with objects of a class. Anyhow, a few people have commented that the appropriate way to abstract this pattern is to wrap it in a function. My question asked for a way to avoid writing a loop every time, and while most people suggest that the loop must be explicitly written, this answer provides some more practical guidance on wrapping that behavior in a function, so I'm accepting it. – Neil Jul 25 '18 at 13:42
  • 2
    Functional programming is more about using functions in the mathematical sense, not just calling functions in preference to other syntactic structures. Most of what is considered "functional" in Python is really stream-oriented programming. – chepner Jul 25 '18 at 13:48
5

Since you're looking for a Pythonic solution, why would even bother trying to adapt map(lambda x: x.process, my_object_list) for Python 3 ?

Isn't a simple for loop enough ?

for x in my_object_list:
    x.process()

I mean, this is concise, readable and avoid creating an unnecessary list if you don't need return values.

scharette
  • 9,437
  • 8
  • 33
  • 67