Elemente löschen aus Objektliste

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Sven7913
User
Beiträge: 1
Registriert: Donnerstag 8. Dezember 2022, 21:39

Hallo allerseits,

ich habe folgendes Problem:

Ich habe eine Liste, welche mittels for-Schleife eine bestimmte Anzahl an Klassenobjekten beinhaltet:

Code: Alles auswählen

orcs = list()
for i in range(20):
    orcs.append(orcObjects.orcInitiateAdult())
    print(orcs[i])
Die Objekte der Klasse orcInitiateAdult beinhalten bestimmte Attribute. Nun möchte ich eine Abfrage einbauen, welche sämtliche Objekte aus der Liste löscht, welche in einem bestimmten Attribut einen bestimmten Wert haben.
Ich habe es mit dieser Funktion versucht:

Code: Alles auswählen

def removeOrc():
    for num in orcs:
        if num.hp == 0:
            orcs.remove(num)
Leider ist das einzige was passiert, dass anscheinend willkürlich ein paar der Objekte in der Liste gelöscht werden, allerdings nicht die, die beim Attribut hp den Wert 0 haben.
Da die Elemente der Liste keinen Namen, sondern lediglich den Speicherplatz bei der automatischen Generierung der Liste erhalten haben (

Code: Alles auswählen

<orcObjects.orcInitiateAdult object at 0x000001467A4B8A60>
), wüsste ich nicht, wie ich sonst gezielt auf einzelne Objekte in der Liste zugreifen könnte. Oder ist es möglich, über eine Funktion herauszufinden, an welcher Stelle in der Liste das einzelne Objekt steht und so das Objekt gezielt aus der Liste zu löschen?
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist ein Standardproblem. Eine Collection zu modifizieren, während man sie traversiert, ist nicht möglich - zumindest mit den gegeben Implementierungen. Das robuster zu machen, würde zu viel kosten.

Aber es geht auch ohne. Der einfachste Weg: neue Liste nur mit den gewünschten Objekten erstellen, und die bleibt erhalten.

Code: Alles auswählen

orcs = [orc for orc in orcs if orc.alive()]
Dein Code suggeriert auch die Verwendung dung globaler Variablen, weil du in deiner Funktion Namen aus dem nichts holst. Gewöhn dir das erst gar nicht an. Übergib Parameter, und gib berechnete Werte per return zurück.
Benutzeravatar
grubenfox
User
Beiträge: 430
Registriert: Freitag 2. Dezember 2022, 15:49

__deets__ hat geschrieben: Donnerstag 8. Dezember 2022, 23:23 Das ist ein Standardproblem. Eine Collection zu modifizieren, während man sie traversiert, ist nicht möglich - zumindest mit den gegeben Implementierungen. Das robuster zu machen, würde zu viel kosten.
Da könnte man noch mal drüber sprechen...
... und hier drüber:

Code: Alles auswählen

def removeOrc():
    for num in orcs[:]:
        if num.hp == 0:
            orcs.remove(num)
Der Unterschied ist das [:] bei der for-Schleife.
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

@grubenfox: soll das im Widerspruch zu meiner Aussage stehen? Du drückst dich eher kryptisch aus. Wenn ja: ist Quatsch. Denn [:] erzeugt nunmal eine Kopie. Also modifiziert man nicht die Collection, über die man iteriert.

Und als Bonus hast du jetzt die Laufzeit gegenüber meinem Ansatz von O(n) auf O(n**2) erhöht, denn auf Listen ist remove seinerseits O(n).
Benutzeravatar
grubenfox
User
Beiträge: 430
Registriert: Freitag 2. Dezember 2022, 15:49

@__deets__: kein Widerspruch, nur als Ergänzung. Ohne Kopie geht es nicht... und [:] ist eine 'billige' [wenig Codeänderungen] Möglichkeit zu einer Kopie einer Liste zu kommen. Mit Nachteilen.
An die Laufzeit hatte ich jetzt noch nicht gedacht, da hatte ich erst mal nur den Speicherverbrauch für die komplette (flache) Kopie der Liste im Hinterkopf. Da hat man bei deinem Ansatz nur Kopien der Objekte die man auch wirklich behalten möchte. Darum auch die Bitte um ein Gespräch.
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Der Unterschied zu meinem Ansatz: der erstellt eine Kopie mit mit den gewünschten Elementen. In einem Rutsch.

Du erstellst eine Kopie, iterierst über die, und entfernst jedes Element mit einer linearen Suche. Dadurch in der Summe quadratisch, und das ist leider wirklich schlecht. Der „Wert“, dass es nur drei Zeichen mehr sind, verblasst dagegen.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@grubenfox: weder der Speicherbedarf, noch die Laufzeit noch die minimale Änderung sind hier entscheiden.
Die entscheidende Frage ist, wie verständlich ist der Code: im einen Fall wird eine Liste mit den gewünschten Objekten erzeugt, im anderen Fall wird eine Kopie erzeugt, um in der ursprünglichen Liste die nicht gewünschten Objekte herauszulöschen. Um das zu verstehen muss man zweimal um die Ecke denken.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sven7913: Die Namensgebung ist für das Verständnis eines Programmcodes sehr wichtig. Irreführende Namen sind noch schlimmer als einfach nur kryptische Namen.
Es gibt in Python eine Namenskonvention, dass Module, Variablennamen und Funktionen komplett_klein geschrieben werden, und Klassen MitGroßenAnfangsbuchstaben.
In Python ist alles ein Objekt, ein Modul orcObjekts zu nennen, wäre ja nur dann sinnvoll, wenn es tatsächlich Dinge enthalten würde, die ausschließlich Orcs benutzen, eine Orc-Axt, ein Orc-Ohrring etc. Da scheint aber ein erwachsener Orc in dem Modul definiert zu werden.
Klassen werden nach Dingen benannt, orcInitiateAdult ist aber eine Tätigkeit. Der passende Name wäre als AdultOrc.
In removeOrc benutzt Du `num` um einen Orc zu bezeichnen. Das ist irreführend, denn mit num würde man eine Zahl assoziieren.
Man benutzt keine kryptischen Abkürzungen. hp -> health_points.
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sven7913: Um das mit den Abkürzungen noch mal zu unterstreichen: `hp` steht natürlich für `hit_points` und hier werden nicht die toten Orcs ausgefiltert, sondern die, die einfach zu erschöpft sind, um noch mal zuzuschlagen. 😉

Was immer es ist, es wäre auch gut wenn man das im Funktionsnamen erkennen könnte. Also `remove_dead_orcs()` oder `remove_exhausted_orcs()` zum Beispiel. Oder `remove_orcs_without_homepages()`. 🤡

Der Namenszusatz `object` oder `objects` mach übrigens nur Sinn falls der ausserhalb der Programmiersprache, also in der Problemdomäne, eine Bedeutung hat. innerhalb von Python ist der sinnfrei. Den könnte man an alles anhängen, denn alles was man an einen Namen binden kann ist in Python ein Objekt. Das im Namen zu erwähnen, bringt dem Leser keinen Mehrwert.

Den Indexzugriff in die Liste würde ich persönlich nicht machen. Man braucht Indexzugriffe in Listen relativ selten. Wenn dann würde ich hier auch nicht `i` benutzen, sondern -1, denn das drückt besser aus, dass man das letzte Element meint, und es funktioniert auch falls man in dem Code nicht mit einer leeren Liste startet. Aber wie gesagt, eigentlich würde ich das ohne Index machen:

Code: Alles auswählen

orcs = []
for _ in range(20):
    orc = AdultOrc()
    print(orc)
    orcs.append(orc)
Mir scheint das aber unnötig umständlich geschrieben zu sein, nur um da ein `print()` für Debuggingzwecke rein zu basteln. Denn eigentlich wäre das erstellen von 20 Orcs eine einfache „list comprehension“:

Code: Alles auswählen

orcs = [AdultOrc() for _ in range(20)]
Da könnte man am Ende die ganze Liste per `print()` ausgeben, oder mit `pprint()` aus dem `pprint()`-Modul in der Standardbibliothek, oder mit `pprint()` oder `cpprint()` aus dem externen `prettyprinter`-Modul.

Oder, falls man wirklich die einzelnen erzeugten Werten zu dem Zeitpunkt sehen will, würde sich so etwas wie das externe `icecream`-Modul anbieten. Mit dessen `ic`-Funktion wird das Objekt nicht nur ausgegeben, sondern auch der Ausdruck der dazu geführt hat, und die Funktion gibt ihr Argument als Ergebnis zurück, so dass man das auch einfach mitten in anderen Ausdrücken, wie beispielsweise der „list comprehension“, anwenden kann.

Code: Alles auswählen

from icecream import ic
...
orcs = [ic(AdultOrc()) for _ in range(20)]
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten