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
kbr
User
Beiträge: 1506
Registriert: Mittwoch 15. Oktober 2008, 09:27

Ja, "Uncle Bob" hat seine Marktlücke gefunden – mit dem diskutiert man daher wohl besser nicht ;)
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

Jede Sprache hat wohl ihre individuellen Eigenschaften, die man eben verstehen muss. :)
Benutzeravatar
__blackjack__
User
Beiträge: 14028
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Für *den* Einsatzzweck ist es egal ob Du `istitle()` oder ein Gegenstück zu `capitalize()` verwendest, denn es geht nur um ein Wort. Da unterscheiden sich `title()` und `capitalize()` nicht. Bei mehr als einem Wort macht es aber einen Unterschied:

Code: Alles auswählen

In [380]: "foo bar".title()                                                     
Out[380]: 'Foo Bar'

In [381]: "foo bar".capitalize()                                                
Out[381]: 'Foo bar'
`main()` definiere ich, wie wohl die allermeisten anderen auch, als letztes, nicht nur aus Gewohnheit beispielsweise aus C oder Pascal, sondern auch aus Konsistenzgründen.

Man kann ja auch der Meinung sein, dass man erst das ”grosse, grobe Bild malt” und die kleineren detaillierteren Teile danach ausfüllt. Also von der Hauptfunktion startend, und dann die Einzelschritte, bis zu Detailaufgaben und Hilfsfunktionen runter, der Quelltext im Laufe der Datei immer spezieller wird. Das Problem bei Python ist, dass man das nicht sauber durchhalten kann, weil das nicht in statische Programmanalyse zuerst und dynamische Laufzeit danach aufgeteilt ist, sondern auch der Modulinhalt ausführbarer Code ist, und alles was man verwendet, vorher definiert sein muss. Und zumindest alles was man für das ausführen von ``class``- und ``def``-Anweisungen benötigt, muss vorher definiert worden sein, auch wenn es kleinere Details sind.

Das betrifft beispielsweise Dekoratoren und alles was man so für Defaultargumente braucht. Also beispielsweise auch Funktionen oder Klassen.

Und dann gibt es auch noch Fälle wo Funktionen nicht per ``def`` definiert werden, sondern aus anderen Funktionen erstellt werden, die dafür vorher aber definiert worden sein müssen, auch wenn das nur Hilfsfunktionen sind, die nicht einmal zur öffentlichen API gehören.

Also beispielsweise würde man ja für eine Cäsar-Chiffre `encode()` und `decode()` vor einer Hilfsfunktion definieren, wenn man vom groben, zum Detail gehen würde. Das kann man machen, wenn man `encode()` und `decode()` tatsächlich als Funktionen hin schreiben will, aber nicht wenn man die beiden Funktionen aus der Hilfsfunktion + Parameter erstellt.

Also das hier geht:

Code: Alles auswählen

def encode(text, key):
    return _code(add, text, key)


def decode(text, key):
    return _code(sub, text, key)


def _code(offset_function, text, key):
    ...
Aber das hier ginge nicht:

Code: Alles auswählen

encode = partial(_code, add)
decode = partial(_code, sub)

def _code(offset_function, text, key):
    ...
Da müsste man dann anfangen gegen die selbst gesteckten Reihenfolgeregeln zu verstossen, oder man schränkt sich freiwillig ein, bestimmte Spracheigenschaften und Möglichkeiten nicht zu nutzen.
„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__
Bei mehr als einem Wort macht es aber einen Unterschied:
verstehe :) lieben Dank und gut zu wissen, denn dann wäre mein vorheriger Funktionsname ohnehin auch noch semantisch falsch/verwirrend gewesen.
Man kann ja auch der Meinung sein, dass man erst das ”grosse, grobe Bild malt” und die kleineren detaillierteren Teile danach ausfüllt. Also von der Hauptfunktion startend, und dann die Einzelschritte, bis zu Detailaufgaben und Hilfsfunktionen runter, der Quelltext im Laufe der Datei immer spezieller wird.
ja genau, das schlägt Hr. Martin vor.

hier mal der gesamte Code, wie ich ihn nun habe:

Auszug Sprueche.txt

Code: Alles auswählen

A
"Abends werden die Faulen fleissig." - Citatboken, Bokförlaget Natur och Kultur, Stockholm, 1967, ISBN 91-27-01681-1
"Abwarten und Tee trinken." - Wander-DSL, Bd. 5, Sp. 702, commons. (Dort zitiert als: "Abwarten und Theetrinken.")
"Adel verpflichtet." (Noblesse oblige) - nach Pierre-Marc-Gaston de Lévis, Maximes et réflections
"Alle Menschen in der Welt streben nur nach Gut und Geld; und wenn sie es dann erwerben legen sie sich hin und sterben." - Citatboken, Bokförlaget Natur och Kultur, Stockholm, 1967, ISBN 91-27-01681-1
"Alle Sünden in eine münden."
"Alle Wege führen nach Rom." - Wander-DSL, Bd. 4, Sp. 1842, commons
"Aller Anfang ist schwer." - Wander-DSL, Bd. 1, Sp. 80, commons
"Aller guten Dinge sind drei." - Wander-DSL, Bd. 1, Sp. 605, commons. (Dort zitiert als: "Aller guten Ding seynd drey.")
"Alles Gute kommt von oben."
"Alles hat seine Zeit, nur die alten Weiber nicht." - Wander-DSL, Bd. 1, Sp. 46, commons
"Alles neu macht der Mai." - nach dem Gedicht von Hermann Adam von Kamp "Alles neu, macht der Mai" (1818)

Code: Alles auswählen

from pathlib import Path


def investigate_sayings():
    # TODO
    # Verbesserung mittels wiki-api - Texte direkt aus Wikipedia "Sprichwörter" auslesen?
    # Methode "tut" zu viel - Zerlegen in Beschaffung der Daten und Erstellen des Datenmodells
    filename = Path("C:/Users/Buchfink/Documents/Projkete/WortspielGenerator/Sprueche.txt")
    with open(filename, "r", encoding="UTF-8") as file:
        investigated_sayings = {}
        for line in file:
            splitted = line.strip().split(".\"")

            saying = splitted[0].strip().replace('\"', '')
            if len(saying) == 1:
                continue

            words = saying.split(" ")
            interesting_words = []
            for word in words:
                if word.istitle():
                    interesting_words.append(word)
            investigated_sayings[saying] = interesting_words
    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):
    substituted_sayings = []
    for key, value in investigated_sayings.items():
        for word in value:
            substituted_word = get_rhyme(word)
            if substituted_word is not None:
                substituted_sayings.append(key.replace(word, substituted_word))
    return substituted_sayings


def show_sayings(sayings):
    for saying in sayings:
        print(saying)
    print(f"Anzahl {len(sayings)}")


def main():
    investigated = investigate_sayings()
    substituted = substitute_with_rhymes(investigated)
    show_sayings(substituted)


if __name__ == '__main__':
    main()
Ich denke, dass das wohl eine pythonische Reihenfolge ist - korrekt?

Am Rande:
Der Code tut was er soll. Aber Ich bin trotzdem ein bisschen unglücklich damit. Hier mal eine Liste all meiner Unglücksfaktoren

1) Ich lese aus einer Datei. Für ein Code-Review ist das blöd, denn man kann den Code dann nicht einfach mal ausprobieren. Ich möchte hier wenn's geht, keine riesigen Aufwände generieren. Dieser Code in investigate_sayings() ist daher schlecht.
Verbesserungsvorschlag: direkt aus Wikipedia auslesen. Ich bin hier aber noch am Recherchieren wie das genau geht und melde mich ggfls. mit konkreten Fragen

2) Das Datenmodell gefällt mir nicht. Es fühlt sich falsch an, einen Spruch als "key" zu missbrauchen. Ein Key mit Leerzeichen oder Umlauten ist irgendwie schräg.
Verbesserungsvorschlag: Klassen, die die Daten halten.
Was meint ihr so?

3) hardcodierte Reim-Zuordnung. Tja nun :) Es ist nur ein Prototyp und ich wollte erst mal sehen, ob das überhaupt eine gute Idee ist. Letztlich musss da aber was generisches hin.

4) Algorithmus zur Substitution: hier könnte man auch flexibler werden und vielleicht mal gucken wollen, ob man statt Reime Synonyme oder Antonyme durchprobiert. :)
Hier mangelt es an Flexibilität aus meiner Sicht.
Verbesserungsvorschlag: auf ein Klassenmodell umziehen und dann mittels Polymorphie (oder Funktion als Parameter?)

5) Algorithmus zum Finden interessanter Wörter: könnte man auch für Verben haben wollen. Nomen sind halt leichter heuristisch einigermaßen erkennbar, daher sind es im ersten Wurf Nomen geworden.

6) Main: könnte man auch mit einem Einzeiler schreiben.
Zum debuggen finde ich es manchmal praktischer, wenn es nicht ganz so kompakt steht. Was meint ihr denn?

7) siehe TODOs im Code selbst.

8) continue.... tja nun... ich bin unentschlossen. Ich mag "continue" eigentlich gar nicht. Besser in zwei Funktionen splitten? Ein If erhöht halt wiederum die Verschachtelungstiefe. Was meint Ihr?

Es gibt noch einen Haufen Aspekte, die man verbessern könnte. Ich poste den Code hier mal, damit Ihr einen Eindruck bekommt, was ich so mache.

Ich freue mich auf Euer Feedback! :)

LG
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@Buchfink,

also dem meisten, was du selbst schon angemerkt hast, würde ich zustimmen.
Unter Punkt 2 würde ich aber nicht auf eine Klasse als Datenbehälter wechseln, solange ein Dictionary ausreicht. man kann ja auch die Struktur anpassen:

Code: Alles auswählen

investigated_sayings = [
    {
        "originalsentence": "...",
        "keywords": [...]
    },
    ...
]
Beim iterieren über dict.items() würde ich nicht "key", "value" sondern aussagekräftige Namen verwenden.
Wenn eine Variable eine Datensequenz enthält würde ich den Plural verwenden:

Code: Alles auswählen

for word in keywords:
"is not None" kann man ersetzen:

Code: Alles auswählen

if substituted_word is not None:

Code: Alles auswählen

if substituted_word:
So etwas schreit nach einer List-Comprehension, Dict-Comprehension, oder Generator-Expression.

Code: Alles auswählen

interesting_words = []
for word in words:
    if word.istitle():
        interesting_words.append(word)
Aber die Funktion investigate_sayings() muss ja sowieso aufgeteilt werden, da kann man die Teile dann vielleicht schon als Generator anlegen.
Zuletzt geändert von rogerb am Montag 1. November 2021, 11:57, insgesamt 1-mal geändert.
nezzcarth
User
Beiträge: 1753
Registriert: Samstag 16. April 2011, 12:47

Zu deinen Punkten 3 und 4: Statt mit harded-coded Zuordnungen zu arbeiten, könntest du natürlich auch Wörterbücher in Dateiform hinterlegen. Aus meiner Sicht ist das für ein Hobby-Projekt um Python zu lernen vermutlich der Ansatz, mit dem du am schnellsten schöne Ergebnisse erhalten wirst. Was auch ganz gut und relativ einfach klappen könnte, ist statt "Voll-Reimen" Assonanzen und/oder Alliterationen/Stabreime zu nehmen. Oder man versucht es ggf. mit anderen heuristischen Ansätzen wie Ähnlichkeitsmaßen (Kölner Phonetik; (gewichtete) Levenshteindistanz, etc.). Wenn man das Erkennen von Endreimen automatisieren und das Problem "NLP-mäßig" angehen will, ist das nämlich nicht trivial und erfordert (denke ich) die Auseinandersetzung mit entsprechenden Algorithmen (z. B. zur Silbentrennung).
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@rogerb und @nezzcarth

ganz lieben Dank für Euren Input.
Unter Punkt 2 würde ich aber nicht auf eine Klasse als Datenbehälter wechseln, solange ein Dictionary ausreicht
ja - genau sowas hab ich gesucht. Ich wusste nicht (wie/dass) das geht. Danke für den Hinweis!
Unter Delphi hätte ich vielleicht eine Klasse gebaut, die eine Generics-Liste aggregiert. Aber das fühlte sich _hier_ auch nicht richtig an, weil zu schwergewichtig. (Manchmal fühlen sich Dinge irgendwie nicht "pythonisch" an... )
"is not None" kann man ersetzen:
--> das hab ich direkt korrigiert.
List-Comprehension, Dict-Comprehension, oder Generator-Expression.
Das werde ich erst googeln müssen, bevor ich konkrete Fragen dazu stellen kann.

Es ist immer wieder erstaunlich, welche Detailfülle sich da manchmal ergibt. :) Aber es macht Spaß von Euch was zu lernen.
Wenn man das Erkennen von Endreimen automatisieren und das Problem "NLP-mäßig" angehen will, ist das nämlich nicht trivial und erfordert (denke ich) die Auseinandersetzung mit entsprechenden Algorithmen (z. B. zur Silbentrennung).
Es ist schwer zu sagen, wo das irgendwann hingeht. Ich finde Sprache als solches tatsächlich ziemlich interessant. (Mein Name ist eine Anspielung auf den Umstand, dass ich andauernd irgendein Buch lese).
Ob ich allerdings intellektuell in der Lage bin, solche Algorithmen dann tatsächlich zu verstehen, steht auf einem anderen Blatt. :)
Ich bin da selbst gespannt wie ein Regenschirm :)

LG
narpfel
User
Beiträge: 690
Registriert: Freitag 20. Oktober 2017, 16:10

@Buchfink: Ich würde an der Stelle keine Liste von Dicts mit implizit festgelegter Struktur benutzen, sondern eher ein `collections.namedtuple` oder auch ein `collections.defaultdict(list)`:

Code: Alles auswählen

In [1]: from collections import namedtuple, defaultdict

In [2]: Saying = namedtuple("Saying", "sentence, keywords")

In [4]: Saying("foo", ["bar"])
Out[4]: Saying(sentence='foo', keywords=['bar'])

# oder

In [5]: sayings = defaultdict(list)

In [6]: sayings["foo"].append("bar")

In [7]: sayings["foo"].append("baz")

In [9]: sayings["spam"].append("eggs")

In [10]: sayings
Out[10]: defaultdict(list, {'foo': ['bar', 'baz'], 'spam': ['eggs']})
Wobei ich eher zum `defaultdict` tendieren würde, weil man darin direkt die Zuordnung eingebaut hat und nicht linear suchen muss, wenn man auf ein bestimmtes Sprichwort zugreifen will. Ein Dict mit beliebigen Strings als Keys ist IMHO nicht komisch.

Ich schreibe zwar auch gerne `if xs` oder `if not xs`, um auf eine leere Collection zu testen, aber `if foo` statt `if foo is not None` schreibe ich nie, weil das auch bei „leeren“ (bzw. allgemein „falsy“) Werten ins `if` springt. „Explicit is better than implicit.“
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@narpfel,
aber `if foo` statt `if foo is not None` schreibe ich nie, weil das auch bei „leeren“ (bzw. allgemein „falsy“) Werten ins `if` springt.
Nicht sicher was du genau mit "leeren Werten" meinst, bei keinem dieser falsy Werte würde der if-Zweig ausgeführt.

Code: Alles auswählen

falsy_values = [list(), dict(), tuple(), range(0), 0.0, 0, False, set(), None, ""]

for thing in falsy_values:
    if thing:
        print(f"{thing} is truthy")
    else:
        print(f"{thing} is falsy")

"""
[] is falsy
{} is falsy
() is falsy
range(0, 0) is falsy
0.0 is falsy
0 is falsy
False is falsy
set() is falsy
None is falsy
 is falsy
"""
Benutzeravatar
__blackjack__
User
Beiträge: 14028
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@rogerb: Genau das war gemeint: das trifft auf viele Werte zu, nicht nur auf `None`. Und vielleicht möchte man das nicht. Oft denkt man an solche Randfälle ja auch nicht. Oder denkt sich, der Fall kann ja eh nicht vorkommen. Und dann ist man stundenlang mit Fehlersuche beschäftigt. :-)
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
narpfel
User
Beiträge: 690
Registriert: Freitag 20. Oktober 2017, 16:10

@rogerb: Flüchtigkeitsfehler, muss natürlich „nicht ins `if` springt“ heißen. Der Punkt ist, dass mit `if foo` nicht zwischen `None` und allgemein falsy unterscheiden werden kann, deswegen besser `if foo is not None`.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@narpfel

dankeschön für die Links und die Tipps

ich vermute, Du meinst _so_?

Code: Alles auswählen

from pathlib import Path
from collections import defaultdict


def investigate_sayings():
    # TODO
    # Verbesserung mittels wiki-api - Texte direkt aus Wikipedia "Sprichwörter" auslesen?
    # Tutorial: https://wdqs-tutorial.toolforge.org/index.php/simple-queries/the-simplest-query/basic-sparql-query/
    # Methode "tut" zu viel - Zerlegen in Beschaffung der Daten und Erstellen des Datenmodells
    filename = Path("C:/Users/Carina/Documents/Projkete/WortspielGenerator/Sprueche.txt")
    with open(filename, "r", encoding="UTF-8") as file:
        investigated_sayings = defaultdict(list)
        for line in file:
            splitted = line.strip().split(".\"")

            saying = splitted[0].strip().replace('\"', '')
            if len(saying) == 1:
                continue

            words = saying.split(" ")
            for word in words:
                if word.istitle():
                    investigated_sayings[saying].append(word)
    return investigated_sayings
Beim Zerlegen der Funktion muss ich auch nochmal genau anschauen, was es mit "with open" _genau_ auf sich hat. Es sorgt wohl dafür (so habe ich die Tutorials verstanden), dass die Datei am Ende wieder geschlossen wird.
Ein Dict mit beliebigen Strings als Keys ist IMHO nicht komisch.
Es kann durchaus sein, dass mein "ungutes Gefühl" daher rührt, dass Umlaute in Delphi ein Problem waren, bevor dort Unicode eingeführt wurde. Wenn das in Python kein Problem ist, dann passt das ja :)

Zu
aber `if foo` statt `if foo is not None` schreibe ich nie, weil das auch bei „leeren“ (bzw. allgemein „falsy“) Werten ins `if` springt
Ich fürchte ich muss hier noch "falsy" googeln, denn den Begriff höre/lese ich heute zum ersten Mal.
Oh je :) Ich weiß soviel _nicht_
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@narpfel
@rogerb: Flüchtigkeitsfehler, muss natürlich „nicht ins `if` springt“ heißen. Der Punkt ist, dass mit `if foo` nicht zwischen `None` und allgemein falsy unterscheiden werden kann, deswegen besser `if foo is not None`.
hm... jetzt bin ich etwas verwirrt.
vielleicht liegt's auch daran, dass ich falsy noch nicht kenne.

`if foo is not None`. --> hört sich für meine Ohren "explizit" an. Und wenn explizit besser ist als implizit, wäre das nach meiner laienhaften Auffassung besser. Aber andererseits klingt es so als wäre diese Schreibweise extrem unüblich.

Sorry, dass ich jetzt hier nochmal nachhaken muss. Ich stehe wohl etwas auf der Leitung.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

falsy sind Dinge, die als falsch gewertet werden. Auch wenn sie nicht der False-wert selbst sind. Zb 0, 0.0, “”, {}, [], () sind alle falsch. Man kann das auch selbst überladen auf eigenen Klassen, mit __nonzero__.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__deets__
vielen lieben Dank für die Erklärung.

Ich hab das hier gefunden (https://developer.mozilla.org/de/docs/Glossary/Falsy) Der Link bezieht sich aber auf JavaScript. Ich hatte auch noch den Beispiel-Code von rogerb ausprobiert. Damit konnte ich es nachvollziehen.
Ich kenne das von Datenbanken so, dass man da aufpassen muss hinsichtlich "Null-Handling".

An "" (leer) hatte ich ehrlich gesagt gar nicht gedacht. Danke für den Hinweis.

Eigentlich möchte ich, dass er nur was in die Liste der substituierten Sprichwörter schreibt, wenn ein Reim gefunden wurde. Wenn aus irgendwelchen Gründen '"" (leer) zurückkäme (oder eben None) soll er nichts tun.

Insofern wäre "if substituted_word:" wohl semantisch besser. Sehe ich das richtig?

Ganz generell muss man meinem obigem Code wohl zum Vorwurf machen, dass er in Punkto Fehlerhandling fragil ist.

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

__nonzero__ wurde in Python 3 allerdings ersetzt durch __bool__, soviel ich weiß.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

@snafu mir war so als ob da was verbessert wurde. Aber da ich davon vielleicht 3 in 20 Jahren umgesetzt habe, ist dieses Detail zu verschwommen :)
Benutzeravatar
__blackjack__
User
Beiträge: 14028
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Aus dem ``split(" ")`` würde ich ein ``split()`` machen. Sonst kann es die leere Zeichenkette tatsächlich geben:

Code: Alles auswählen

In [418]: "foo  bar".split(" ")                                                 
Out[418]: ['foo', '', 'bar']
Das wird durch `istitle()` zwar rausgefiltert, aber bei so Freitext würde ich eher auf Nummer sicher gehen.

Das Aufteilen an '."' finde ich auch ein bisschen gewagt. Keine Sprüche die mit einem anderen Satzzeichen enden können? Rethorische Fragen? Ausrufe? Und wenn die '."'-Kombination später noch mal kommt, ist's auch komisch mit `split()`. Ich würde da eher `partition()` verwenden. Dann kann man auch einfach testen ob der Trenner überhaupt vorkam. Falls der nämlich nicht vorkommt, nimmt der Code einfach die komplette Zeile.

Warum ersetzen von '"' in der ganzen Zeile und nicht nur am Anfang? Den Test auf `len()` und 1 mag ich auch nicht so besonders. Das ist irgendwie so "indirekt" das da die Zeilen mit den einzelnen Buchstaben erwischt werden.

Ich hätte es wahrscheinlich in diese Richtung geschrieben:

Code: Alles auswählen

def investigate_sayings():
    # TODO
    # Verbesserung mittels wiki-api - Texte direkt aus Wikipedia "Sprichwörter" auslesen?
    # Tutorial: https://wdqs-tutorial.toolforge.org/index.php/simple-queries/the-simplest-query/basic-sparql-query/
    # Methode "tut" zu viel - Zerlegen in Beschaffung der Daten und Erstellen des Datenmodells
    filename = Path(
        "C:/Users/Carina/Documents/Projkete/WortspielGenerator/Sprueche.txt"
    )
    with open(filename, "r", encoding="UTF-8") as file:
        investigated_sayings = defaultdict(list)
        for line_number, line in enumerate(file, 1):
            if line.startswith('"'):
                saying, seperator, _ = line[1:].partition('."')
                if seperator:
                    for word in saying.split():
                        if word.istitle():
                            investigated_sayings[saying].append(word)
                else:
                    print(
                        f"Fehler: Ende von Spruch in Zeile {line_number} nicht gefunden."
                    )
            else:
                line = line.rstrip()
                if not (len(line) == 1 and line.isalpha()):
                    print(
                        f"Fehler: Unerwartete(s) Zeichen in Zeile {line_number}."
                    )

    return investigated_sayings
Wobei ich kein Fan von Fehlermeldungen und weitermachen bin, ich hätte das wahrscheinlich als Ausnahmen gemacht und abgebrochen wenn da was kommt was nicht ins erwartete Schema passt.

Und `print()` wäre eher Logging, wenn es keine Ausnahmen sein sollen. Standardbibliothek oder `loguru`.
„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__

oh woow, dankeschön!
Aus dem ``split(" ")`` würde ich ein ``split()`` machen. Sonst kann es die leere Zeichenkette tatsächlich geben:
Im Hinblick auf das Fehlermanagement habe ich leider noch nicht geschafft, die ganzen Randfälle zu testen (Und ja, selbstredend gehört sowas dazu :)). Bzw. mir sind auch vermutlich die Fallstricke der einzelnen Konstellationen noch nicht immer ganz klar. Oder mit anderen Worten: mir mangelt es noch an Erfahrung im Hinblick auf Python.

Wobei ich kein Fan von Fehlermeldungen und weitermachen bin, ich hätte das wahrscheinlich als Ausnahmen gemacht und abgebrochen wenn da was kommt was nicht ins erwartete Schema passt.
ja ich tendiere auch eher zu Exceptions, da es sich bei dieser Funktion um "Backend"*-Logik handelt.
Wobei ich ehrlich gesagt nicht weiß, ob "print(..)" im Backend in Python akzeptiert/üblich ist.
Im konkreten Fall ist das zum Verständnis aber vermutlich ok, nehme ich an.

*) irgendjemand sagte mir mal, dass der Begriff Backend mit Vorsicht zu genießen sei. Ich habe aber leider die Begründung vergessen.


Aktuell versuche ich noch den zweiten Vorschlag von rogerb einzubauen. Da "kämpfe" ich noch mit den Tutorials, aber ich glaube, dass ich jetzt weiß, was er meinte.

LG
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@Buchfink,

Code: Alles auswählen

Aktuell versuche ich noch den zweiten Vorschlag von rogerb einzubauen. Da "kämpfe" ich noch mit den Tutorials, aber ich glaube, dass ich jetzt weiß, was er meinte.
Beim zweiten Hinschauen, würde ich vielleicht ganz auf das Erstellen dieser Datenstruktur verzichten. Ich will dich aber nicht davon abhalten es trotzdem mal zu versuchen.
"investigated_sayings" enthält keine zusätzliche Informationen. Du verbindest lediglich Daten, die zusammengehören in einer neuen Datenstruktur. Das kann ineffizient werden.

Ausschlaggebend ob ein Wort zu den "interesting_words" gehört hängt davon ab ob es im Dictionary der Reimwörter vorhanden ist. Mit istitle() bekommst du auch viele Wörter, für die es später gar kein Reimwort gibt.
Daher wäre es besser nicht istitle() abzufragen, sondern "ist in Reimwörter". Dann gibt "get_rhyme()" auch immer etwas Verwertbares zurück. Jetzt wird ja des Öfteren None zurückgegeben, was dann wieder die Prüfung gegen None überhaupt erst nötig macht.
Man kann auch beim Iterieren über die Sprichwörter direkt die Umwandlung vornehmen. Dadurch dürfte eine Iteration über den gesamten Datensatz wegfallen.

Da ich jetzt bei __blackjack__ auch das "is not None" sehe, noch ein Kommentar dazu: Natürlich würde jeder erstmal dem Satz "explicit is better than implicit" zustimmen. Aber hier sehe ich es etwas anders. None enthält den Informationswert: "Kein Reimwort gefunden". Das muss aber nicht zwingend an None gebunden sein. Dafür kommt jeder falsy Wert in Frage. Hier ist es nur zufällig None, da das der default Wert für dict.get() ist. Würde man sich später für eine andere Vorgehensweise entscheiden, müsste man von "if is not None" zu "if is not other_falsy_value" ändern. Durch die dynamische Typisierung von Python hat man hier den Vorteil, dass man sich gerade nicht festlegen muss und statt dessen auf alle falsy-Werte eine Antwort hat. Aber das muss man im Einzelfall abwägen und ich hatte ja schon gesagt, dass man diese Abfrage komplett auslassen kann.
Antworten