Seiteneffekt im Dictionary?!

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • Hallo,

    für ein Spiel möchte ich CSV Dateien auswerten und dabei Grafiken erstellen.

    Jede Zeile von 156 - 172 erstellt eine Grafik, jede Zeile für sich funktioniert auch einzeln.

    Sobald ich jedoch (z.B) 156 und 157 gleichzeitig "aktiv" habe, so glückt die Erstellung der ersten Grafik, die 2. Grafik ist jedoch kaputt (kaputt deshalb, weil die Spieler in der Grafik mehrfach vorkommen).

    1. Grafik

    2. Grafik

    So kam ich auf die Idee dass es einen ungewollten Seiteneffekt im Dictionary gibt, da ich dieses bei der Erstellung der Grafik noch verändere, also habe ich zu Beginn der Funktion in Zeile 110 ein deepcopy() eingebaut um dies zu verhindern. Leider ohne den gewünschten Erfolg. Warum?

  • Zur hilfreichsten Antwort springen
  • Der Fehler kam dadurch zustande, dass row eine leere Liste war.

    Black + isort + sorcery

  • Ich hab den Pfad geaendert und kriege "index error".

    Als Text oder als Traceback, denn ein try/except index error ist schon eingebaut.


    Danke ich kenn black, nur gefällt mir das nicht und deswegen werde ich das auch weiterhin nicht verwenden.

    Das mit if not row: continue ist jedoch ne gute Idee.

    Leider beantwortet es aber nicht meine Frage, wie der Seiteneffekt entsteht.

    Andersrum: was soll das Script machen?

    Solch Grafiken wie oben gezeigt erstellen für die unterschiedlichen Schadenswerte (Zeile 156-172)

  • Danke ich kenn black, nur gefällt mir das nicht und deswegen werde ich das auch weiterhin nicht verwenden.

    Es gibt noch grey.

    Leider beantwortet es aber nicht meine Frage, wie der Seiteneffekt entsteht.

    Am dict scheint es nicht zu liegen, denn in den Funktionen schaden_pro_runde und gesammtschaden stimmen die Ausgaben der Namen überein.

    Code
    print(list(c_teilnehmer.keys()))

    Ich vermute, dass es mit dem Plotten zusammenhängt. Es können auch gar keine doppelten Einträge im dict existieren, weswegen jeder Name nur einmal vorkommen kann. Ich vermute, dass matplotlib vom vorherigen Plot die Labels mit übernimmt.

    • Hilfreichste Antwort

    Loesche den Plot, dann klappt es:

  • Man sollte einfach gar nicht diese gruselige funktionsbasierte Schnittstelle verwenden die auf globalem Zustand basiert, und man eben auch mit den üblichen Problemen rechnen muss die durch globalen Zustand entstehen können. Diese Schnittstelle ist hauptsächlich da weil Benutzer diesen Rotz von älteren, anderen Programmen zur Erstellung von Plots gewohnt sind, und das geringfügig praktischer ist, wenn man das interaktiv in einer Python-Shell verwendet.

    In `csv_auswerten()` ist ein unnötiges ``pass``.

    In die Funktion sollte kein leeres Wörterbuch übergeben werden um das zu füllen. Die Funktion sollte das selbst erstellen und dem Aufrufer dann das ausgefüllte Wörterbuch als Rückgabewert liefern.

    `bericht` verkompliziert den Code für jeden verarbeiteten Datensatz unnötig. Statt entweder das Flag oder den Datensatz zu prüfen ob der relevante Bereich angefangen hat, würde man das besser *vor* der Schleife klären, welche dann die Datensätze verarbeitet und sich nicht darum kümmern braucht ob noch nach dem Anfang gesucht wird. Kriterium ist auch nicht ob "Runde" *irgendwo* im Datensatz vorkommt, sondern ob es das erste Feld ist. Sonst fällt das auf die Nase wenn sich jemand "Runde" nennt und das deshalb auch in den Zeilen vor den Rundendaten in einem Feld steht.

    Literale Zeichenketten sind keine Kommentare. Kommentare sind ausschliesslich durch ``#`` gekennzeichnet.

    Folgender Code ist zu umständlich:

    Python
                if row[3] in teilnehmer and row[7] in teilnehmer:
                    schaden_addieren(row, teilnehmer)
                else:
                    if not row[3] in teilnehmer and not row[3] == "--":
                        teilnehmer.update({row[3]: Statistik()})
                    if not row[7] in teilnehmer and not row[7] == "--":
                        teilnehmer.update({row[7]: Statistik()})
                    schaden_addieren(row, teilnehmer)

    Als erstes gehört gleicher Code der in allen Zweigen am Anfang oder Ende steht, *einmal* vor beziehungsweise hinter das Konstrukt:

    Python
                if row[3] in teilnehmer and row[7] in teilnehmer:
                    pass
                else:
                    if not row[3] in teilnehmer and not row[3] == "--":
                        teilnehmer.update({row[3]: Statistik()})
                    if not row[7] in teilnehmer and not row[7] == "--":
                        teilnehmer.update({row[7]: Statistik()})
                
                schaden_addieren(row, teilnehmer)

    Jetzt macht der ``if``-Zweig aber gar nichts mehr. Und im ``else``-Zweig wird ist das *Gegenteil* von der Bedingung im jetzt leeren ``if``-Zeig. Also ist das ”äussere” ``if``/``else`` komplett überflüssig:

    Python
                if not row[3] in teilnehmer and not row[3] == "--":
                    teilnehmer.update({row[3]: Statistik()})
                if not row[7] in teilnehmer and not row[7] == "--":
                    teilnehmer.update({row[7]: Statistik()})
                
                schaden_addieren(row, teilnehmer)

    Die ``not`` stehen an der falschen Stelle oder sind überflüssig und `update()` ist der falsche Weg um *einem* Schlüssel einen Wert zuzuordnen:

    Python
                if row[3] not in teilnehmer and row[3] != "--":
                    teilnehmer[row[3]] = Statistik()
                if row[7] not in teilnehmer and row[7] != "--":
                    teilnehmer[row[7]] = Statistik()
    
                schaden_addieren(row, teilnehmer)

    Im Grunde steht da aber nun zweimal das gleiche, nur der Index ist unterschiedlich:

    Python
                for name in (row[index] for index in [3, 7]):
                    if name not in teilnehmer and name != "--":
                        teilnehmer[name] = Statistik()
    
                schaden_addieren(row, teilnehmer)

    Letztlich ist das aber alles umständlicher als einfach ein `collections.defaultdict` zu verwenden.

    In `schaden_addieren()` ist viel ähnlicher Code den man durch eine Schleife über die Unterschiede ersetzen kann.

    `addiere_schaden()` ist etwas umständlich wenn der `typ` immer der Attributname ist. Da kann man doch einfach `getattr()` verwenden. Damit wird die Methode zu einem Einzeiler.

    Zwischenstand:

    Das mit den `MAX_RUNDEN` ist komisch gelöst. Letztlich sieht das auch so aus als wäre das mit Pandas und vielleicht `seaborn` einfacher zu lösen die Daten zu filtern, gruppern, aggregieren.

    “Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”

  • Hallo,

    vielen Dank für eure Hilfe und vielen Dank __blackjack__ für die konstruktive Kritik an meinem Code und am zeigen wie man es besser machen kann.

    Zu meinem ursprünglichen Problem war tatsächlich die Löschung mit plt.clf(). Was wieder sehr gut zeigt wie pöse das böse Global ist. Es wär mir auch nicht bewusst gewesen, dass dies auf globalen Zustand basiert. So oft verwende ich matplotlib nicht und wenn, orientiere ich mich an vorhandene Beispiele und bin dann froh wenn die Grafik so aussieht wie ich es mir vorgestellt hatte.

    In `csv_auswerten()` ist ein unnötiges ``pass``.

    Das war enthalten um einen Breakpoint in PyCharm zu setzen

    Sonst fällt das auf die Nase wenn sich jemand "Runde" nennt und das deshalb auch in den Zeilen vor den Rundendaten in einem Feld steht.

    Stimmt, an das hätte ich gar nicht gedacht das sich so jemand auch nennen kann :lol:


    Literale Zeichenketten sind keine Kommentare. Kommentare sind ausschliesslich durch ``#`` gekennzeichnet.

    Aber damit lassen sich Codeblöcke schnell und einfach auskommentieren. Kennt jemand eine Tastenkombination für PyCharm die vor dem markierten Text in jeder Zeile in # setzt/entfernt?


    Da kann man doch einfach `getattr()` verwenden.

    getattr() habe ich tatsächlich erst kennen gerlernt während dem Schreiben des Skriptes als ich auf der Suche war ich ich die Funktion schaden_pro_runde(teilnehmer, title, name) für alle Fälle verwenden kann, da es mir unpraktisch erschien, für jeden Fall eine eigene Funktion zu erstellen. Rückwirkend hätte man getattr() dann schon an weiteren Stellen des Skriptes verwenden könnnen ja :thumbup:

    Dein Skript ist schon sehr stark gekürzt (abstrahiert), dafür fehlt mir die Erfahrung. Auch musste ich das jetzt erstmal Schritt für Schritt debuggen um teils zu verstehen was wo passiert ;)

    Danke für eure Zeit und Hilfe :thumbup:

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!