Wie rufe ich dieselbe Methode für eine Liste von Objekten auf?

78

Angenommen, Code wie folgt:

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,.....]

Jetzt möchte ich Methoden start und stop (vielleicht auch andere) für jedes Objekt in der Liste all aufrufen. Gibt es eine elegante Möglichkeit, dies zu tun, außer eine Reihe von Funktionen wie zu schreiben?

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

def stop_all(all):
Dmitry
quelle
1
eine Reihe von Funktionen ist zwei Funktion?
SilentGhost
4
Dies hat nichts mit Ihrer Frage zu tun, aber es gibt keinen offensichtlichen Grund in Ihrem Beispiel, warum Sie diese Basisklasse benötigen. Python wird Ihnen sehr gerne eine Liste nicht verwandter Objekte zur Verfügung stellen. Solange alle über Start- und Stoppmethoden verfügen, können Sie diese weiterhin durchlaufen und die Methoden aufrufen.
Duncan
1
Sie definieren Basemit nutzlosen Methoden und definieren dann das Verhalten in Python Aund Bspiegeln ein schlechtes Design wider. Anstatt eine abstrakte Basisklasse zu verwenden, können Sie sie einfach definieren Aund Baustauschbar verwenden, sofern sie eine Schnittstelle gemeinsam nutzen. Ihre derzeitige Vorgehensweise schafft eine nutzlose Klasse, die nur zusätzliche Dinge sind, die Sie nicht benötigen.
Mike Graham

Antworten:

7

Die * _all () -Funktionen sind so einfach, dass ich für einige Methoden nur die Funktionen schreiben würde. Wenn Sie viele identische Funktionen haben, können Sie eine generische Funktion schreiben:

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

Oder erstellen Sie eine Funktionsfactory:

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")
...
Ameisen Aasma
quelle
56
Dies scheint mir nicht einfacher zu sein, als die for-Schleife direkt zu schreiben.
Mike Graham
177

Das wird funktionieren

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

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

einfaches Beispiel

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

und in python3

all = ["MILK","BREAD","EGGS"]
list(map(lambda x:x.lower(),all))
>>>['milk','bread','eggs']
Mark Essel
quelle
9
In Python 3 gibt map ein Kartenobjekt zurück, keine Liste wie in Python 2. Ich bin mir der Standardprozedur nicht sicher, aber ich dachte, ich sollte sie korrigieren, also habe ich eine Bearbeitung eingereicht. Der Code sollte nun sowohl in Python 2 als auch in Python 3 wie erwartet und korrekt
funktionieren
1
Auch dies scheint die pythonischere Antwort zu sein, es sollte die akzeptierte sein. Karte ist eine gute Wahl, und ich habe noch nie zuvor Lambdas verwendet, also danke.
leetNightshade
6
In Python 3 reicht ein map()Funktionsaufruf nicht aus. Es wird ein Generatorobjekt erstellt, aber kein Lambda auf jedes Listenelement angewendet. Lambda wird beim Durchlaufen des map () - Ergebnisses angewendet. list(map(...))wird das Problem beheben.
Alex Musayev
24
@dreikanter oder einfach nur [x.start() for x in all]?
Endolith
6
Eine Liste zu verwenden und sie dann wegzuwerfen, kommt mir unpythonisch vor. Da es sich bei der aufgerufenen Methode um zwingenden Code mit Nebenwirkungen handelt, ist eine explizite forSchleife idiomatischer und für den Leser weniger überraschend. Wenn Sie wirklich müssen, könnten Sie schreiben def foreach(fun, gen): for i in gen: fun(gen).
Thomas
32

Es scheint, als gäbe es einen pythonischeren Weg, dies zu tun, aber ich habe ihn noch nicht gefunden.

Ich verwende manchmal "map", wenn ich dieselbe Funktion (keine Methode) für eine Reihe von Objekten aufrufe:

map(do_something, a_list_of_objects)

Dies ersetzt eine Reihe von Code, der folgendermaßen aussieht:

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

Kann aber auch mit einer Fußgängerschleife "for" erreicht werden:

  for obj in a_list_of_objects:
       do_something(obj)

Der Nachteil ist, dass a) Sie eine Liste als Rückgabewert aus "map" erstellen, die gerade weggeworfen wird, und b) es möglicherweise verwirrender ist als nur die einfache Schleifenvariante.

Sie könnten auch ein Listenverständnis verwenden, aber das ist auch ein bisschen missbräuchlich (noch einmal, eine Wegwerfliste erstellen):

  [ do_something(x) for x in a_list_of_objects ]

Für Methoden würde wohl eine dieser Methoden funktionieren (mit den gleichen Vorbehalten):

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

oder

[ x.method_call() for x in a_list_of_objects ]

In Wirklichkeit denke ich, dass die "for" -Schleife für Fußgänger (und dennoch effektiv) wahrscheinlich die beste Wahl ist.

abonet
quelle
4
Meine erste Reaktion war map (lambda x: x.member (), list). Es fühlte sich wie ein ziemlich klarer Einzeiler an
Mark Essel
Guter Abschluss über den "Fußgänger" für Schleife. Wenn dies CL wäre, hätten Sie mapcarund mapcum zwischen den Fällen "Liste wollen / Liste wegwerfen" zu unterscheiden. Aber leider ...
Joao Tavora
21

Die Vorgehensweise

for item in all:
    item.start()

ist einfach, leicht, lesbar und prägnant. Dies ist der Hauptansatz, den Python für diese Operation bereitstellt. Sie können es sicherlich in eine Funktion einkapseln, wenn das etwas hilft. Das Definieren einer speziellen Funktion für den allgemeinen Gebrauch ist wahrscheinlich weniger klar als nur das Ausschreiben der for-Schleife.

Mike Graham
quelle
7

Vielleicht map, aber da Sie keine Liste erstellen möchten, können Sie Ihre eigene schreiben ...

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

dann können Sie tun:

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

Übrigens ist alles eine eingebaute Funktion, überschreibe sie nicht ;-)

fortran
quelle
6

Ab Python 2.6 gibt es eine operator.methodcaller- Funktion.

So können Sie etwas eleganteres (und schnelleres) erhalten:

from operator import methodcaller

map(methodcaller('method_name'), list_of_objects)
notbad.jpeg
quelle
1
Was ist der beste Ansatz für Python3?
Neil
@Neil dies funktioniert in Python3, der einzige Unterschied ist, dass mapein Iterator in 3 zurückgegeben wird, keine Liste
notbad.jpeg
3

Wenn Sie die Antwort von @Ants Aasmas noch einen Schritt weiter gehen, können Sie einen Wrapper erstellen, der jeden Methodenaufruf akzeptiert und an alle Elemente einer bestimmten Liste weiterleitet:

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

Diese Klasse kann dann folgendermaßen verwendet werden:

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()

Welches erzeugt die folgende Ausgabe:

foo: foo
foo: bar
foo: quux!

Mit etwas Arbeit und Einfallsreichtum könnte es wahrscheinlich erweitert werden, um auch Attribute zu verarbeiten (Rückgabe einer Liste von Attributwerten).

Joachim Sauer
quelle