Dmitry
Dmitry

Reputation: 1121

How to call same method for a list of objects?

Suppose code like this:

class Base:
    def start(self):
        pass
    def stop(self)
        pass

class A(Base):
    def start(self):
        ... do something for A
    def stop(self)
        .... do something for A

class B(Base):
    def start(self):

    def stop(self):

a1 = A(); a2 = A()
b1 = B(); b2 = B()

all = [a1, b1, b2, a2,.....]

Now I want to call methods start and stop (maybe also others) for each object in the list all. Is there any elegant way for doing this except of writing a bunch of functions like

def start_all(all):
    for item in all:
        item.start()

def stop_all(all):

Upvotes: 111

Views: 144621

Answers (10)

user966939
user966939

Reputation: 719

I'm a bit surprised no one has mentioned it so far, but if a one-liner is what you're after, Python does permit for-loops on one line:

for i in ['a','b','c']: print( i.upper() )

Granted, this obviously does not follow your typical Python syntax, but in some scenarios you might actually want multiple loops like these close together for a bit more concise code, where it would make a lot of sense. E.g.:

for e in checkboxes: e.click()
for e in text_inputs: e.send_keys('test')
for e in selects: e.select(0)

Unlike list substitutions, this also prevents unnecessary use of memory if you don't actually need to create a list.

Upvotes: 0

Mortasen
Mortasen

Reputation: 157

The best solution, in my opinion, depends on whether you need the result of the method and whether your method takes any arguments except self.

  • If you don't need the result, I would simply write a for loop:
for instance in lst:
    instance.start()
  • If you need the result, but method takes no arguments, I would use map:
strs = ['A', 'B', 'C']
lower_strs = list(map(str.lower, strs))  # ['a', 'b', 'c']
  • And finally, if you need the result and method does take some arguments, list comprehension would work great:
strs = ['aq', 'bq', 'cq']
qx_strs = [i.replace('q', 'x') for i in strs]  # ['ax', 'bx', 'cx']

Upvotes: 0

Aleksei Petrenko
Aleksei Petrenko

Reputation: 7188

If you would like to have a generic function while avoiding referring to method name using strings, you can write something like that:

def apply_on_all(seq, method, *args, **kwargs):
    for obj in seq:
         getattr(obj, method.__name__)(*args, **kwargs)

# to call:
apply_on_all(all, A.start)

Similar to other answers but has the advantage of only using explicit attribute lookup (i.e. A.start). This can eliminate refactoring errors, i.e. it's easy to rename the start method and forget to change the strings that refer to this method.

Upvotes: 0

notbad.jpeg
notbad.jpeg

Reputation: 3368

Starting in Python 2.6 there is a operator.methodcaller function.

So you can get something more elegant (and fast):

from operator import methodcaller

map(methodcaller('method_name'), list_of_objects)

Upvotes: 7

Mark Essel
Mark Essel

Reputation: 4646

This will work

all = [a1, b1, b2, a2,.....]

map(lambda x: x.start(),all)    

simple example

all = ["MILK","BREAD","EGGS"]
map(lambda x:x.lower(),all)
>>>['milk','bread','eggs']

and in python3

all = ["MILK","BREAD","EGGS"]
list(map(lambda x:x.lower(),all))
>>>['milk','bread','eggs']

Upvotes: 202

abonet
abonet

Reputation: 784

It seems like there would be a more Pythonic way of doing this, but I haven't found it yet.

I use "map" sometimes if I'm calling the same function (not a method) on a bunch of objects:

map(do_something, a_list_of_objects)

This replaces a bunch of code that looks like this:

 do_something(a)
 do_something(b)
 do_something(c)
 ...

But can also be achieved with a pedestrian "for" loop:

  for obj in a_list_of_objects:
       do_something(obj)

The downside is that a) you're creating a list as a return value from "map" that's just being throw out and b) it might be more confusing that just the simple loop variant.

You could also use a list comprehension, but that's a bit abusive as well (once again, creating a throw-away list):

  [ do_something(x) for x in a_list_of_objects ]

For methods, I suppose either of these would work (with the same reservations):

map(lambda x: x.method_call(), a_list_of_objects)

or

[ x.method_call() for x in a_list_of_objects ]

So, in reality, I think the pedestrian (yet effective) "for" loop is probably your best bet.

Upvotes: 42

Joachim Sauer
Joachim Sauer

Reputation: 308061

Taking @Ants Aasmas answer one step further, you can create a wrapper that takes any method call and forwards it to all elements of a given list:

class AllOf:
    def __init__(self, elements):
        self.elements = elements
    def __getattr__(self, attr):
        def on_all(*args, **kwargs):
            for obj in self.elements:
                getattr(obj, attr)(*args, **kwargs)
        return on_all

That class can then be used like this:

class Foo:
    def __init__(self, val="quux!"):
        self.val = val
    def foo(self):
        print "foo: " + self.val

a = [ Foo("foo"), Foo("bar"), Foo()]
AllOf(a).foo()

Which produces the following output:

foo: foo
foo: bar
foo: quux!

With some work and ingenuity it could probably be enhanced to handle attributes as well (returning a list of attribute values).

Upvotes: 4

Mike Graham
Mike Graham

Reputation: 76693

The approach

for item in all:
    item.start()

is simple, easy, readable, and concise. This is the main approach Python provides for this operation. You can certainly encapsulate it in a function if that helps something. Defining a special function for this for general use is likely to be less clear than just writing out the for loop.

Upvotes: 30

Ants Aasma
Ants Aasma

Reputation: 54882

The *_all() functions are so simple that for a few methods I'd just write the functions. If you have lots of identical functions, you can write a generic function:

def apply_on_all(seq, method, *args, **kwargs):
    for obj in seq:
         getattr(obj, method)(*args, **kwargs)

Or create a function factory:

def create_all_applier(method, doc=None):
    def on_all(seq, *args, **kwargs):
        for obj in seq:
            getattr(obj, method)(*args, **kwargs)
    on_all.__doc__ = doc
    return on_all

start_all = create_all_applier('start', "Start all instances")
stop_all = create_all_applier('stop', "Stop all instances")
...

Upvotes: 8

fortran
fortran

Reputation: 76067

maybe map, but since you don't want to make a list, you can write your own...

def call_for_all(f, seq):
    for i in seq:
        f(i)

then you can do:

call_for_all(lamda x: x.start(), all)
call_for_all(lamda x: x.stop(), all)

by the way, all is a built in function, don't overwrite it ;-)

Upvotes: 7

Related Questions