Code-Review zur Thematik "Konstanten"

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.
Benutzeravatar
__blackjack__
User
Beiträge: 14027
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Das Aufsetzen vom Logging gehört da eher nicht rein. Das vermischt man nicht mit der Programmlogik. Eine der wenigen Sachen die man manchmal auch schon auf Modulebene macht, wenn man das schon für Code auf Modulebene haben möchte, beispielsweise für die Initialisierung von Konstanten, oder ansonsten als erstes in der Hauptfunktion.

In der Logging-Meldung würde man nicht selbst so etwas wie "Fehler" schreiben. Das wäre die Aufgabe des Loggers die Stufe/den Schweregrad auszugeben, und bei "Fehler" wäre das dann auch nicht `logging.debug()` sondern `logging.error()`. Oder wenn es nicht ganz so schwerwiegend ist `logging.warning()`.

Die Pfade/Dateinamen würde ich hier als Konstanten setzen, denn wenn die nicht als Argumente übergeben werden, dann stehen die ja irgendwo mitten im Code und man muss die erst suchen wenn man sie ändern möchte.

Und da ist in der Tat einiges an Redundanz. Du benutzt `Path` immer nur um schon komplette Pfade in Form von Zeichenketten zu verpacken, und nicht um die aufzubauen.

Die Namen sind auch falsch herum. Ein `path_log` ist ein Log in dem Pfade protokolliert werden. Ein `log_path` ist ein Pfad wo Logdateien liegen oder der Pfad einer Logdatei.

Also eher so etwas:

Code: Alles auswählen

    base_path = Path("Daten", "WortspielGenerator")
    sayings_file_path = base_path / "Sprueche_raw.txt"

    logs_path = base_path / "logs"
    logs_path.mkdir(parents=True, exist_ok=True)
    log_file_path = logs_path / "Sprueche_ausgemustert.txt"
Wobei wie gesagt mindestens `base_path` als Konstante oder als Argument sinnvoller wäre.

Falls das Ganze nur zum Anlegen einer Datei mit fehlerhaften Einträgen aus der Sprüchedatei ist, würde ich da kein Logging für benutzen bzw. IMHO missbrauchen.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__blackJack__
vielen Dank!

ich hatte Dich in Deinem Beitrag vom 02.11.2021 - 00:03
Und `print()` wäre eher Logging, wenn es keine Ausnahmen sein sollen. Standardbibliothek oder `loguru`.
so verstanden, dass Du es für sinnvoll hälst, statt "print()" Logging zu verwenden.

Daher bin ich nun etwas verunsichert, was Du mit
Falls das Ganze nur zum Anlegen einer Datei mit fehlerhaften Einträgen aus der Sprüchedatei ist, würde ich da kein Logging für benutzen bzw. IMHO missbrauchen.
genau meinst.

Vermutlich handelt es sich um ein Missverständnis. Bzw. was wäre Dein Ratschlag in diesem Fall?

Im Grunde "brauche" ich die fehlerhaften Einträge nicht zwingend. Es ist natürlich nützlich, da reinschauen zu können und zu sehen, wieviel "Datenmüll" in der Ursprungsdatei steht. (Das ist durchaus einiges und ich konnte dank Deines Tipps mit dem Logging auch einige Erkenntnisse gewinnen)
Das vermischt man nicht mit der Programmlogik.
Da bin ich voll bei Dir. Mich hat es auch gestört, dass die Methode einfach zu viel macht.
In der Logging-Meldung würde man nicht selbst so etwas wie "Fehler" schreiben. Das wäre die Aufgabe des Loggers die Stufe/den Schweregrad auszugeben, und bei "Fehler" wäre das dann auch nicht `logging.debug()` sondern `logging.error()`.
Edit:
ja stimmt! Das habe ich nun erst beim zweiten Lesen Deines Beitrags kapiert.
Und da ist in der Tat einiges an Redundanz. Du benutzt `Path` immer nur um schon komplette Pfade in Form von Zeichenketten zu verpacken, und nicht um die aufzubauen.
ja, genau. Danke für den Code-Schnipsel. Ich wusste leider nicht, dass das _so_ geht. Aber so sieht es gleich viel schöner aus!
Benutzeravatar
__blackjack__
User
Beiträge: 14027
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Der Code jetzt sieht halt so aus als würde nur in der Funktion das Logging benutzt um eine Datei mit fehlerhaften Einträgen aus der zugehörigen Textdatei zu erzeugen, statt allgemein Informationen ob nun einfach so Informationen, oder Warnungen, oder Fehler auszugeben. Logging muss ja auch nicht zwingend in eine Datei geschrieben werden. Das heisst bei `print()` durch Logging ersetzen hätte ich jetzt nicht zwingend erwartet, dass Du eine Datei schreibst. Das einfachste wäre ja tatsächlich erst einmal wirklich nur aus den `print()`\s entsprechende Logging-Aufrufe zu machen die dann auch weiterhin die Informationen erst einmal einfach ausgeben. Da ist der Vorteil gegenüber `print()`, dass man einfach und zentral regeln kann welche Level da dann tatsächlich ausgegeben werden. Insbesondere beim DEBUG-Level, was zur Fehlersuche ja nützlich ist, aber für normale Programmläufe in der Regel ja eher nervig.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

Hallo @__blackjack__

soweit habe ich im ersten Moment gar nicht gedacht, obwohl ich ähnliche Logging-Mechanismen kenne (die z.B. in eine DB-schreiben)
(Wald, Bäume,...)

Die Datei hatte für mich den "augenscheinlichen" Vorteil, dass die Fehler erst mal isoliert (in der Datei) betrachtet werden konnten. Das war im konkreten Fall durchaus praktisch.
Insbesondere beim DEBUG-Level, was zur Fehlersuche ja nützlich ist, aber für normale Programmläufe in der Regel ja eher nervig.
ja das ist korrekt.
Im konkreten Beispiel würde ich auf DEBUG-Informationen im Log tatsächlich verzichten. Mit meinem Kollegen habe ich das Thema auch schon diskutiert. Die Meinungen gehen da teilweise etwas auseinander.
Aus Interesse: In welchen Fällen würdest Du DEBUG-Infos nutzen?

(Ich baue die meist anlassbezogen ein oder weil es sich um einen Vorgang handelt, der bei Fehlern z.B. zu Datenverlust führen kann)
Benutzeravatar
__blackjack__
User
Beiträge: 14027
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Auch anlassbezogen. Und es kommt natürlich auch auf die Art der Anwendung an. Also ob das ein Programm ist das ich selbst ausführe oder jemand anderes, und auch ob das ein Dienst oder eine Webanwendung ist, wo andere Leute die Fehler irgendwann triggern, und man nicht so genau weiss wie genau, bis man sich die Aufzeichnungen angeschaut hat.

Beim selber ”live” Fehler suchen, verwende ich auch gerne mal `icecream.ic()`. Eher weniger das `snoop`-Modul, weil mir das schon zu viel Info ist, aber ich könnte mir vorstellen, dass Anfänger das interessant finden könnten.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@sirius3

danke, der Code sieht so gleich viel lesbarer aus. Hab's nun mal eingebaut. Das war genau der Schnitt, den ich gesucht habe! :)

Mir fällt der Einsatz der Generatoren leider immer noch ein bisschen schwer. Ich hoffe, dass ich das noch in meinem Schädel bald mal hineinbekomme.
Ich merke zwar, _dass_ das ein Fall für einen Generator ist. Aber wenn ich's einbauen möchte, gucke ich teilweise wie ein Schwein ins Uhrwerk.:,)

Hoffentlich kriege ich das bald besser hin.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

hallo zusammen

und hier mal ein aktueller Zwischenstand.

Ist das aus Eurer Sicht gut lesbar?
Seht ihr Redundanzen?

Code: Alles auswählen

from pathlib import Path
from collections import defaultdict
import logging
logger = logging.getLogger(__name__)

BASE_PATH = Path("Daten", "WortspielGenerator")


def initialize_logging():
    logs_path = BASE_PATH / "logs"
    logs_path.parent.mkdir(parents=True, exist_ok=True)
    log_file_path = logs_path / "Sprueche_ausgemustert.txt"
    logging.basicConfig(filename=str(log_file_path), level=logging.ERROR)


def read_sayings(filename):
    with open(filename, "r", encoding="UTF-8") as file:
        for line_number, line in enumerate(file, 1):
            if line.startswith('"'):
                saying, seperator, _ = line[1:].partition('"')
                if seperator:
                    yield saying
                else:
                    logging.error(f"Ende von Spruch in Zeile {line_number} nicht gefunden: {line.strip()}")
            else:
                line = line.rstrip()
                if not (len(line) == 1 and line.isalpha()):
                    logging.error(f"Unerwartete(s) Zeichen in Zeile {line_number}: {line}.")


def investigate_sayings(filename):
    # TODO
    # Verbesserung mittels wiki-api - Texte direkt aus Wikipedia "Sprichwörter" auslesen?
    # Tutorials:
    #  - https://wdqs-tutorial.toolforge.org/index.php/simple-queries/the-simplest-query/basic-sparql-query/
    #  - https://docs.python.org/3/howto/logging.html#logging-basic-tutorial

    investigated_sayings = defaultdict(list)
    for saying in read_sayings(filename):
        for word in saying.split():
            if word.istitle():
                investigated_sayings[saying].append(word)

    return investigated_sayings


def get_rhyme(word):
    # TODO
    # Quelle ist noch unklar. Daher nur mal exemplarische simplifizierte Beispielzuordnung
    # Ungelöste Probleme:
    #  - Quelle selbst, hardcodiert ist ja keine Lösung :)
    #  - Artikelproblem - gleicher Artikel funktioniert immer, unterschiedliche Artikel eher weniger (kontextabhängig)
    #  - es gibt ggf. mehrere Reimwörter pro Wort,
    #  - Plural vs. Singular
    #  - Beidseitigkeit - sprich Laus reimt sich auf Maus und umgekehrt. Man könnte vereinfachen, indem man
    #    jedes Pärchen nur einmal einfügt
    rhymes = {
        "Esel": "Sessel", "Sessel": "Esel",
        "Geld": "Held", "Held": "Geld",
        "Spiegel": "Igel",  "Igel": "Spiegel",
        "Kopf": "Topf", "Topf": "Kopf",
        "Baum": "Raum", "Raum": "Baum",
        "Berg": "Zwerg", "Zwerg": "Berg",
        "Laus": "Maus", "Maus": "Laus",
        "Adel": "Sargnagel", "Sargnagel": "Adel",
        "Wunsch": "Punsch", "Punsch": "Wunsch",
        "Flügel": "Kleiderbügel",
        "Waffen": "Karaffen",
        "Macht": "Verdacht"
    }
    rhyme = rhymes.get(word)
    return rhyme


def substitute_with_rhymes(investigated_sayings):
    for key, value in investigated_sayings.items():
        for word in value:
            substituted_word = get_rhyme(word)
            if substituted_word:
                yield key.replace(word, substituted_word)


def show_sayings(sayings):
    for saying in sayings:
        print(saying)


def main():
    initialize_logging()
    sayings_file_path = BASE_PATH / "Sprueche_raw.txt"
    investigated = investigate_sayings(sayings_file_path)
    substituted = substitute_with_rhymes(investigated)
    show_sayings(substituted)


if __name__ == '__main__':
    main()
Ich hoffe, dass ich alles richtig verstanden habe.
@__blackJack__ So wie ich das nun herausgefunden habe, kann man durch das Herausnehmen von "initialize_logging()" die Ausgabe des Logging in der Console erhalten. :) Schön, dass das so einfach geht.



Als nächstes schaue ich mir mal an, wie ich das Mock mit den Reimen ersetze.
Benutzeravatar
__blackjack__
User
Beiträge: 14027
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Bei den `logging`-Aufrufen könnte man das Formatieren der Zeichenketten auf tatsächlich getätigte Log-Aufrufe beschränken, statt das auch zu machen wenn die Meldung letztlich gar nicht gebraucht wird.

In `investigate_sayings()` hätte ich `extend()` mit einem Generatorausdruck verwendet. Statt ``for``-Schleife, ``if``, und `append()`.

Die Redundanz in `rhymes` hast Du ja schon selbst kommentiert. Und mir fällt bei näherem hinsehen auch auf, dass das gar nicht vollständig ist. Die letzten Einträge sind gar nicht ”gespielgelt”.

In `substitute_with_rhymes()` sind die Namen `key` und `value` zu generisch. Das ist ja keine Funktion die allgemein mit irgendwelchen Schlüssel/Wert-Paaren operiert, über die man an der Stelle nichts über deren Bedeutung weiss.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import logging
from collections import defaultdict
from pathlib import Path

logger = logging.getLogger(__name__)

BASE_PATH = Path("Daten", "WortspielGenerator")


def initialize_logging():
    logs_path = BASE_PATH / "logs"
    logs_path.parent.mkdir(parents=True, exist_ok=True)
    log_file_path = logs_path / "Sprueche_ausgemustert.txt"
    logging.basicConfig(filename=str(log_file_path), level=logging.ERROR)


def read_sayings(filename):
    with open(filename, "r", encoding="UTF-8") as file:
        for line_number, line in enumerate(file, 1):
            if line.startswith('"'):
                saying, seperator, _ = line[1:].partition('"')
                if seperator:
                    yield saying
                else:
                    logging.error(
                        "Ende von Spruch in Zeile %d nicht gefunden: %r",
                        line_number,
                        line,
                    )
            else:
                line = line.rstrip()
                if not (len(line) == 1 and line.isalpha()):
                    logging.error(
                        "Unerwartete(s) Zeichen in Zeile %d: %r",
                        line_number,
                        line,
                    )


def investigate_sayings(filename):
    #
    # TODO Verbesserung mittels wiki-api - Texte direkt aus Wikipedia
    #   "Sprichwörter" auslesen?
    #
    # Tutorials:
    #  - https://wdqs-tutorial.toolforge.org/index.php/simple-queries/the-simplest-query/basic-sparql-query/
    #  - https://docs.python.org/3/howto/logging.html#logging-basic-tutorial
    #
    investigated_sayings = defaultdict(list)
    for saying in read_sayings(filename):
        investigated_sayings[saying].extend(
            word for word in saying.split() if word.istitle()
        )

    return investigated_sayings


def get_rhyme(word):
    #
    # TODO Quelle ist noch unklar. Daher nur mal exemplarische simplifizierte
    #   Beispielzuordnung.
    #
    #   Ungelöste Probleme:
    #     - Quelle selbst, hardcodiert ist ja keine Lösung :)
    #     - Artikelproblem - gleicher Artikel funktioniert immer,
    #       unterschiedliche Artikel eher weniger (kontextabhängig)
    #     - es gibt ggf. mehrere Reimwörter pro Wort,
    #     - Plural vs. Singular
    #
    rhymes = {
        "Adel": "Sargnagel",
        "Baum": "Raum",
        "Berg": "Zwerg",
        "Esel": "Sessel",
        "Flügel": "Kleiderbügel",
        "Geld": "Held",
        "Kopf": "Topf",
        "Laus": "Maus",
        "Macht": "Verdacht",
        "Spiegel": "Igel",
        "Waffen": "Karaffen",
        "Wunsch": "Punsch",
    }
    rhymes.update((word_b, word_a) for word_a, word_b in rhymes.items())
    return rhymes.get(word)


def substitute_with_rhymes(investigated_sayings):
    for saying, words in investigated_sayings.items():
        for word in words:
            substituted_word = get_rhyme(word)
            if substituted_word:
                yield saying.replace(word, substituted_word)


def show_sayings(sayings):
    for saying in sayings:
        print(saying)


def main():
    initialize_logging()
    sayings_file_path = BASE_PATH / "Sprueche_raw.txt"
    investigated = investigate_sayings(sayings_file_path)
    substituted = substitute_with_rhymes(investigated)
    show_sayings(substituted)


if __name__ == "__main__":
    main()
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__blackJack__
lieben Dank!

ich werde das morgen abend genauer sichten könne. Ich hab bisher aber schon einen Diff gemacht und gesehen, dass ein paar Details anders sind. Da werde ich morgen genauer drauf eingehen.

zu NLP habe ich das hier gefunden:
https://course.spacy.io/de/

Es lädt aber noch runter :) dauert wohl ncoh
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__blackJack__

ich gehe mal die Änderungen im Code von oben nach unten durch:

1) Reihenfolge der Importe

Danke für den Hinweis (unter https://pep8.org/#imports ... da steht's ja auch, wie es sein sollte)
Hier hatte ich mich ein wenig drauf verlassen, das PyCharm mir eine "falsche" Reihenfolge anzeigt. Dies ist aber nicht der Fall.
Ich habe das nun korrigiert.

2) Logging, Formatieren der Zeichenketten
Hab ich auch übernommen. Hier muss ich (morgen) mal (aus Neugier) messen, was das performance-technisch ausmacht. Bei großen Logs kann das ja ein Unterschied machen. Und dann wäre das gut, im Hinterkopf zu haben.
Oder habe ich da jetzt was missverstanden?

3) extend
es sieht jetzt pythonischer aus, da kompakter. :-) Mangels täglicher Übung kriege ich bei den Generatoren leider immer noch einen Knoten ins Hirn. :-(
Hier muss ich mich im Urlaub nochmal dran setzen, um das _richtig_ zu verstehen.

4) Reime
Die letzten Einträge sind gar nicht ”gespielgelt”.
stimmt. Ich fürchte das liegt daran, dass es sich "nur" um ein Mock handelt und ich beim Tippen irgendwann gemerkt habe, dass ich hierfür eine generische Lösung brauche, wenn ich mir nicht das Fleisch von den Fingern tippen will.
Des Weiteren ist es ja eine n:m-Zuordnung. Daher war ich auch etwas unschlüssig, ob ich mit "Wort-Pärchen" gut beraten bin. Das Endergebnis meiner Überlegung war jedenfalls, dass es hier evtl. schon was Fertiges geben könnte, denn allein dieses Thema dürfte ja durchaus ein größeres Projekt sein.

Am Rande:

Code: Alles auswählen

rhymes.update((word_b, word_a) for word_a, word_b in rhymes.items())
wird leider mit folgender Meldung quittiert:

Code: Alles auswählen

Traceback (most recent call last):
  File "C:/Users/buchfink/PycharmProjects/pyQT_3/beispielWortspielGenerator_4.py", line 114, in <module>
    main()
  File "C:/Users/buchfink/PycharmProjects/pyQT_3/beispielWortspielGenerator_4.py", line 110, in main
    show_sayings(substituted)
  File "C:/Users/buchfink/PycharmProjects/pyQT_3/beispielWortspielGenerator_4.py", line 101, in show_sayings
    for saying in sayings:
  File "C:/Users/buchfink/PycharmProjects/pyQT_3/beispielWortspielGenerator_4.py", line 95, in substitute_with_rhymes
    substituted_word = get_rhyme(word)
  File "C:/Users/buchfink/PycharmProjects/pyQT_3/beispielWortspielGenerator_4.py", line 87, in get_rhyme
    rhymes.update((word_b, word_a) for word_a, word_b in rhymes.items())
  File "C:/Users/buchfink/PycharmProjects/pyQT_3/beispielWortspielGenerator_4.py", line 87, in <genexpr>
    rhymes.update((word_b, word_a) for word_a, word_b in rhymes.items())
RuntimeError: dictionary changed size during iteration

Process finished with exit code 1
ich hatte zunächst vermutet, dass dieser Fehler entstanden ist, weil ich den Diff sukzessive übernommen habe und evtl. was vergessen oder falsch übertragen habe, aber wenn ich den Code so wie er oben steht, ausprobiere, erhalte ich auch diesen Fehler.
So wie ich die Meldung verstehe, stört er sich daran, dass er innerhalb eines Generators die Dict-Größe ändert.
Stimmt meine Vermutung?

5) Namen `key` und `value` zu generisch
Asche auf mein Haupt. Das kommt mir bekannt vor. Ich glaube, das hattest Du bereits in einem der Beiträge im November angemerkt. Augenscheinlich habe ich das vergessen :-( Das war nicht beabsichtigt.
mit "saying" und "words" ist es jedenfalls besser zu lesen :)

6) Main
Mir ist aufgefallen, dass Du "__main__" statt '__main__' schreibst.
Leider komme ich da manchmal durcheinander und schreibe mal doppelte Anführungszeichen und mal einfach Hochkommata. So wie ich das verstehe, ist beides erlaubt. Aber gibt's denn diesbezüglich Favoriten? Oder ist es egal?

Vielen lieben Dank für Deine Anmerkungen!

LG
Benutzeravatar
__blackjack__
User
Beiträge: 14027
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Ich habe keine Ahnung wie viel das mit der Formatierung beim Logging bringt, ich wollte es nur mal gezeigt haben, und eventuell macht einen eine statische Code-Analyse auch darauf aufmerksam.

Zur Ausnahme: Gut das ich ”ungetestet” drüber geschrieben habe. :-) `items()` ist ja ein ”view”, das heisst das spiegelt auch Veränderungen im Wörterbuch wieder, was diesen Ausdruck im besten Fall mehrdeutig werden lassen kann und im schlechtesten Fall endlos, denn wenn man während man über den view iteriert neue Werte einträgt, müssten die ja auch wieder in die Iteration eingehen. Darum ist das verboten. Man müsste da also entweder eine „list comprehension“ aus dem Generatorausdruck machen, oder explizit mittels `list()` über eine Kopie vom Ergebnis von `items()` iterieren.

Früher™ habe ich literale Zeichenketten in einfache Anführungszeichen eingefasst, weil mir das ”natürlich” vorkam, weil die `repr()`-Darstellung das benutzt. Heute verwende ich die doppelten Anführungszeichen weil mich die Argumente von Black (dem Quelltextformatierungsprogramm) überzeugt haben die zu verwenden, obwohl das lustigerweise eine der ganz wenigen Einstellungsmöglichkeiten von dem Programm ist.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__blackjack__
Danke für den Hinweis.
Man müsste da also entweder eine „list comprehension“ aus dem Generatorausdruck machen, oder explizit mittels `list()` über eine Kopie vom Ergebnis von `items()` iterieren.
Ich habe nun der Übung halber mal beides mit einem Jupyter-Notebook probiert. (Die print()-Befehle dienen daher nur zu Testzwecken und müssen im Produktiv-Code natürlich raus)

Ich nehme an mit "Kopie" meinst Du das hier?

Alternative "Kopie"

Code: Alles auswählen

rhymes_simple = {
        "Adel": "Sargnagel",
        "Baum": "Raum",
        "Berg": "Zwerg",
        "Esel": "Sessel",
        "Flügel": "Kleiderbügel",
        "Geld": "Held",
        "Kopf": "Topf",
        "Laus": "Maus",
        "Macht": "Verdacht",
        "Spiegel": "Igel",
        "Waffen": "Karaffen",
        "Wunsch": "Punsch",
    }

rhymes = {}
rhymes.update((word_a, word_b) for word_a, word_b in rhymes_simple.items())
rhymes.update((word_b, word_a) for word_a, word_b in rhymes_simple.items())
print(rhymes)
print(len(rhymes))
Diese Variante liefert das gewünschte Ergebnis.

Bei der List-Comprehension bin ich jedoch gescheitert. Ich habe drei Tutorials zu Rate gezogen (z.B. https://python.plainenglish.io/list-com ... 6dd4a54b57). Leider habe ich offensichtlich einen (Denk-)Fehler gemacht:

Alternative "list comprehension"

Code: Alles auswählen

rhymes_simple = {
        "Adel": "Sargnagel",
        "Baum": "Raum",
        "Berg": "Zwerg",
        "Esel": "Sessel",
        "Flügel": "Kleiderbügel",
        "Geld": "Held",
        "Kopf": "Topf",
        "Laus": "Maus",
        "Macht": "Verdacht",
        "Spiegel": "Igel",
        "Waffen": "Karaffen",
        "Wunsch": "Punsch",
    }
    
rhymes = {(word_a, word_b) for (word_a, word_b) in rhymes_simple.items() for (word_b, word_a) in rhymes_simple.items()}
print(rhymes)
print(len(rhymes))
Bei Alternative "list comprehension" ist die Liste leider nur 12-elementig, statt 24-elementig. :-(

So wie ich die Tutorials verstehe, sollte obiger Code "entsprechend" zu folgendem Schnipsel sein

Code: Alles auswählen

rhymes_simple = {
        "Adel": "Sargnagel",
        "Baum": "Raum",
        "Berg": "Zwerg",
        "Esel": "Sessel",
        "Flügel": "Kleiderbügel",
        "Geld": "Held",
        "Kopf": "Topf",
        "Laus": "Maus",
        "Macht": "Verdacht",
        "Spiegel": "Igel",
        "Waffen": "Karaffen",
        "Wunsch": "Punsch",
    }
    
rhymes = {}
for (word_a, word_b) in rhymes_simple.items():
    rhymes[word_a] = word_b
    for (word_b, word_a) in rhymes_simple.items():
        rhymes[word_a] = word_b
print(rhymes)
print(len(rhymes))
Hier entsteht jedoch wie erwartet eine 24-elementige Liste.

Irgendwas scheint hinsichtlich meines Verständnisses bzw. meiner Annahmen falsch zu sein. Ich habe auch mal ausprobiert, ob meine Annahme hinsichtlich der verschachtelten for-Schleifen vielleicht falsch ist. Aber auch das hier führt zu einer 24-elementigen Liste

Code: Alles auswählen

rhymes_simple = {
        "Adel": "Sargnagel",
        "Baum": "Raum",
        "Berg": "Zwerg",
        "Esel": "Sessel",
        "Flügel": "Kleiderbügel",
        "Geld": "Held",
        "Kopf": "Topf",
        "Laus": "Maus",
        "Macht": "Verdacht",
        "Spiegel": "Igel",
        "Waffen": "Karaffen",
        "Wunsch": "Punsch",
    }
    
rhymes = {}
for (word_a, word_b) in rhymes_simple.items():
    rhymes[word_a] = word_b
for (word_b, word_a) in rhymes_simple.items():
    rhymes[word_a] = word_b
print(rhymes)
print(len(rhymes))
Ich bin nun etwas ratlos, denn mir scheinen die list comprehensions eine elegante Möglichkeit für dieses Problem zu sein. (Auch wenn dieses Mock vermutlich die nächsten Tage nicht überleben wird :))

obwohl das lustigerweise eine der ganz wenigen Einstellungsmöglichkeiten von dem Programm ist.
Danke für den Tipp. Ich hab mal in PyCharm geschaut, allerdings scheint es dort nichts zu geben. Interessehalber - was sind denn die Argumente von Black?

LG
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Du machst das viel zu kompliziert. rhymes_simple.items() liefert doch schon den passenden Iterator. Also reicht:

Code: Alles auswählen

rhymes.update(rhymes_simple.items())
Und da die update()-Methode auch mit anderen Dicts umgehen kann, reicht sogar ein:

Code: Alles auswählen

rhymes.update(rhymes_simple)
Die Schlüssel-Wert-Paare werden dann automatisch gezogen.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@snafu

lieben dank für Deine Antwort. Leider bin ich nun etwas verwirrt. Ggf. übersehe ich etwas und man kann das auch ganz anders lösen.

Wenn ich Deinen Vorschlag umsetze, erhalte ich eine 12-elementige Liste (und nicht wie gewünscht eine 24-elementige).

Ich möchte, dass alle Pärchen als Key-Value-Paare auch in "rumgedrehter" Richtung eingefügt werden.*)

Beispiel:
Wenn ich "get_rhyme("Geld") aufrufe, soll "Held" zurück geliefert werden - und umgekehrt: get_rhyme("Held") ---> soll "Geld" zurückgeben.

*)Perspektivisch wird die oben mit get_ryhme() gemockte "Lösung" ja gänzlich rausfliegen. Aber interessehalber möchte ich den Vorschlag von __blackjack__ trotzdem sehr gerne verstehen.
Benutzeravatar
__blackjack__
User
Beiträge: 14027
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Das von snafu war ja auch nur die halbe Lösung, also wie man die eine Hälfte vereinfachen kann.

Mit Kopie meinte ich einfach `list()` aufrufen um eine Kopie zu machen die vom Wörterbuch unabhängig ist:

Code: Alles auswählen

# anstatt (fehlerhaft):
rhymes.update((word_b, word_a) for word_a, word_b in rhymes.items())

# vorher Kopie der Items:

rhymes.update((word_b, word_a) for word_a, word_b in list(rhymes.items()))

# oder per „list comprehension“ erst eine Liste aufbauen und die `update()` übergeben:

rhymes.update([(word_b, word_a) for word_a, word_b in rhymes.items()])
In beiden Fällen wird dann nicht mehr gleichzeitig über die den „view“ der Schlüssel/Wert-Paare iteriert *und* das `update()` ausgeführt, und damit das Wörterbuch verändert.

Die Black Dokumentation zu doppelten Anführungszeichen:
Why settle on double quotes? They anticipate apostrophes in English text. They match the docstring standard described in PEP 257. An empty string in double quotes ("") is impossible to confuse with a one double-quote regardless of fonts and syntax highlighting used. On top of this, double quotes for strings are consistent with C which Python interacts a lot with.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__blackjack__

Danke!
Wie doof von mir, da bin ich gedanklich leider unterwegs "falsch abgebogen". Mein Denkfehler war, dass ich "update" als Lösungsmöglichkeit ausgeklammert hatte.

zu den Anführungszeichen:
Das klingt schlüssig. In Delphi werden Strings von einfachen Hochkommata umschlossen. Ich vermute, dass das auch ein Grund ist, weshalb ich das hin und wieder etwas wild vermische :-(
Benutzeravatar
__blackjack__
User
Beiträge: 14027
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ja, da falle ich gelegentlich drauf rein wenn ich nach längerer Zeit mal wieder was in Pascal schreibe. So auch dieses Jahr für den Advent of Code. Obwohl Borland Pascal 7 in der letzten DOS-Version ja sogar einen milden Anfall von Syntax-Highlighting in der IDE hat, schreibe ich manchmal Zeichenketten mit ". Witzig ist dann der Hilfetext zum lapidaren „Syntax Error“ Nummer 5: „You might have forgotten the quotes around a string constant.“ 😀
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

Borland Pascal 7 kenne ich (leider oder zum Glück? ;-) ) nicht. Ich bin bei Delphi 5 "eingestiegen".
Wir wechseln alle 2 Jahre auf das neueste Delphi. Das Syntax-Highlighting ist aber auch in der jüngsten IDE nach wie vor spärlich. Man kann es allerdings mit Addons aus der Community aufmotzen (wenn man die Zeit hat, sich mit den Bugs zu befassen, die man sich damit ggf. reinholt)
Benutzeravatar
__blackjack__
User
Beiträge: 14027
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Meine ”optische Reise” mit Pascal unter DOS:

Turbo Pascal 5:
Bild

Turbo Pascal 6:
Bild

Borland Pascal 7:
Bild

Im ersten Beispiel würde man das nicht kompilieren können, weil Version 6 noch keine ASM-Blöcke kannte.

Version 7 kennt farblich Kommentare, reservierte Worte, Zahlen, Zeichenketten, ”Symbole” (Punkte, Klammern, Gleichheitszeichen, und so weiter; im Grunde alles was nicht Ziffer oder Buchstabe ist), und ASM-Blöcke wo nur zwischen Kommentar und nicht-Kommentar unterschieden wird. Das Symbole und der Inhalt von ASM-Blöcken beide hellgrün sind, muss nicht so sein. Und man könnte auch für alles eine andere Hintergrundfarbe wählen, wo die Standardeinstellung überall Blau ist.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

oh, die "Verwandtschaft" zu Delphi ist unverkennbar.
spannender Vergleich :)
Ich fühle mich bei #13#10 und "end." auch direkt ein bisschen "zu Hause"
Und F1 - die Hilfe! :) Ich vermute, in Pascal 7 war die noch schnell?

Bei uns wurde bis vor ca. 11 Jahren auch noch produktiv Software mit Pascal geschrieben. (Kann man sich heute fast nicht mehr vorstellen....)
Die Software existiert im Kern immer noch, jetzt aber in Delphi.

Interessanterweise hat sich am Syntax-Highlithing in Delphi (verglichen mit dem Pascal 7- Screenshot) nichts viel getan.
Schlüsselwörter (begin, end etc.), Strings, Kommentare und normaler Quellcode sind unterschiedlich "eingefärbt".
Antworten