Zeile aus Datei auslesen mit Mindestlänge

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
steff01
User
Beiträge: 2
Registriert: Sonntag 2. Oktober 2022, 07:40

Hallo,

meine Frage ist:
Ich versuche aus einer Datei mit einer Wortliste eine bis mehrere Zeile auszulesen.
Dabei soll aber die jeweilige Zeile mindestens x Zeichen haben.
Siehe unten beim "while" und "mindestlaenge"
Leider krieg ich es nicht hin. Muss ich da eine Rekursion machen, also daraus eine Funktion machen und immer wieder aufrufen bis es klappt? Lieg ich da richtig, oder geht das ohne Rekursion?
Aktuell geht es nur solange Zeilen mit der entsprechenden Länge gefunden wurde ansonsten wird abgebrochen. Ich steh echt auf dem "Schlauch"

Code: Alles auswählen

def passwort_erzeugen():
    label_erzeugtes_passwort.configure(bg="SystemButtonFace")
    entry_erzeugtes_passwort.configure(bg="white")
    # aus einer Wörterliste
    time_start = datetime.now()
    if notebook_art_passwort.index("current") == 1:
        trennzeichen = str(entry_trennzeichen.get())
        mindestlaenge = int(spinbox_mindestlaenge_worter.get())
        anzahl_worter = int(spinbox_anzahl_worter.get())
        if entry_wortliste.get() == "":
            messagebox.showerror("Fehler", "es wurde keine Wortliste angegeben")
            return
        wortliste = entry_wortliste.get()
        word_list = []
        fileencoding = "utf-8"
        # lese die datei in eine liste zeile für zeile
        # noinspection PyUnusedLocal
        with open(wortliste) as f:
            words = [line.strip() for line in codecs.open(wortliste, "r", fileencoding)]
        tmpcount = 1
        while tmpcount <= anzahl_worter:
            s = random.choice(words)
            if len(s) >= mindestlaenge:
                print(s)
                word_list.append(s)
                tmpcount = tmpcount + 1
            else:
                #messagebox.showerror("Fehler", "Mindestlänge nicht erreicht, es wird abgebrochen")
                return
        s = trennzeichen.join(word_list)

        # zufällige Zahl zwischen 0 und 9 am Ende des Passwortes anfügen
        if var_zahl_am_ende.get():
            zuf_zahl = str(random.randint(0, 9))
            s = s + zuf_zahl
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

Verwende vernünftige Namen in deinem Programm. Sie sind wichtig für den Leser um zu verstehen, was der Code tut. Dieser Leser bist im Zweifelsfall du, denn auch du wirst in 2 Tagen nicht mehr wissen, was der Unterschied zwischen "wortliste" und "word_list" und "words" ist. Von "s" gar nicht zu reden.
Der Datentyp hat in dem Namen nichts zu suchen. Für Listen geht man in der Regel so vor, dass man die Pluralform von dem als Namen verwendet, was als Elementen darin liegt. Ist es eine Liste mit words, dann heißt die auch "words". Und wenn es eine Liste der möglichen Worte ist, dann vielleicht possible_words.

Wenn du schon eine List-Comprehension verwendest, um "words" zu füllen, warum steckst du da dann alle Werte rein und nicht nur die, die die Mindestlänge haben?
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Du benutzt einige Variablen, die gar nicht definiert sind, label_erzeugtes_passwort, entry_erzeugtes_passwort, notebook_art_passwort, entry_trennzeichen, spinbox_mindestlaenge_worter, spinbox_anzahl_worter, entry_wortliste und var_zahl_am_ende. Das sollten alles Argumente der Funktion sein, doch 8 Argumente sind recht unübersichtlich, so dass man sie am besten in ein komplexeres Objekt zusammenfasst; GUI-Programme brauchen zwangsläufig eigene Klassendefinitionen.
`time_start` wird gar nicht verwendet, der Name ist auch etwas verwirrend, weil ja nicht nur Zeit sondern ein Datum mit enthalten ist.
Es ist etwas seltsam, dass die Funktion davon abhängt, welches Notebook gerade aktiv ist.
Das eigentliche Problem, ein Passwort zu erzeugen, sollte von der GUI getrennt sein, so dass Du eine Funktion hast, die so aussehen könnte:

Code: Alles auswählen

def passwort_erzeugen(wortliste, trennzeichen, mindestlaenge, anzahl_worter, zahl_am_ende):
    word_list = []
    fileencoding = "utf-8"
    # lese die datei in eine liste zeile für zeile
    # noinspection PyUnusedLocal
    with open(wortliste) as f:
        words = [line.strip() for line in codecs.open(wortliste, "r", fileencoding)]
    tmpcount = 1
    while tmpcount <= anzahl_worter:
        s = random.choice(words)
        if len(s) >= mindestlaenge:
            print(s)
            word_list.append(s)
            tmpcount = tmpcount + 1
        else:
            #messagebox.showerror("Fehler", "Mindestlänge nicht erreicht, es wird abgebrochen")
            return
    s = trennzeichen.join(word_list)

    # zufällige Zahl zwischen 0 und 9 am Ende des Passwortes anfügen
    if zahl_am_ende:
        zuf_zahl = str(random.randint(0, 9))
        s = s + zuf_zahl
    return s
`wortliste` ist eigentlich ein Dateiname, also `wortliste_dateiname`. Du öffnest die Datei dann zweimal, einmal in einem with-Statemant, was Du aber gar nicht benutzt und nochmal, mit codecs.open, wobei Du diese Datei nicht mehr schließt. codecs.open ist eigentlich nicht mehr nötig, weil open auch schon die nötigen Parameter kennt.
`fileencoding` als Variable ist komisch, entweder ist das wirklich variabel, dann muß die Information von außen kommen, oder es ist konstant, dann kann man die Konstante gleich direkt bei open angeben.

Code: Alles auswählen

    # lese die datei in eine liste zeile für zeile
    with open(wortliste_dateiname, encoding="utf-8") as lines:
        words = [line.strip() for line in lines]
Die Liste `word_list` wird etliche Zeilen vor dem Punkt initialisiert, wo sie gebraucht wird. Variablen sollten immer erst dann eingeführt werden, wenn man sie auch braucht.
`tmpcount` ist überflüssig. Hier wird keine Temperatur gezählt, sondern eigentlich Wörter. Und diese Anzahl ist einfach nur die Länge der Liste. Das `return` im else-Block ist wohl nicht das gewollte, weil Du ja zu kurze Wörter nur ignorieren willst, aber nicht gleich ganz abbrechen.

Code: Alles auswählen

    word_list = []
    while len(word_list) <= anzahl_worter:
        word = random.choice(words)
        if len(word) >= mindestlaenge:
            word_list.append(word)
Was ist eine `zuf_zahl`? War das fehlende `all` zu teuer?
Inzwischen sieht der Code so aus:

Code: Alles auswählen

def passwort_erzeugen(wortliste_dateiname, trennzeichen, mindestlaenge, anzahl_worter, zahl_am_ende):
    # lese die datei in eine liste zeile für zeile
    with open(wortliste_dateiname, encoding="utf-8") as lines:
        words = [line.strip() for line in lines]

    word_list = []
    while len(word_list) <= anzahl_worter:
        word = random.choice(words)
        if len(word) >= mindestlaenge:
            word_list.append(word)
    passwort = trennzeichen.join(word_list)

    # zufällige Zahl zwischen 0 und 9 am Ende des Passwortes anfügen
    if zahl_am_ende:
        password += str(random.randint(0, 9))
    return password
Nun könnte man erst die Wortliste nach den Wörtern der richtigen Länge filtern, das macht das zufällige Auswählen einfacher:

Code: Alles auswählen

def passwort_erzeugen(wortliste_dateiname,
        trennzeichen, mindestlaenge, anzahl_worter, zahl_am_ende):
    # lese die datei in eine liste zeile für zeile
    with open(wortliste_dateiname, encoding="utf-8") as lines:
        stripped_lines = (line.strip() for line in lines)
        word_list = [word for word in stripped_lines
            if len(word) >= mindestlaenge]

    words = random.choices(word_list, k=anzahl_worter)
    passwort = trennzeichen.join(words)

    # zufällige Zahl zwischen 0 und 9 am Ende des Passwortes anfügen
    if zahl_am_ende:
        password += str(random.randint(0, 9))
    return password
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@steff01: Ergänzend zu den Anmerkungen von sparrow: Manche schlechte Namen kann man sich auch sparen wenn man nicht jedes Zwischenergebnis an einen Namen bindet.

Statt Warnungen die auf einen *Fehler* hinweisen mit so etwas wie ``# noinspection PyUnusedLocal`` ruhig zu stellen, bitte über die Meldung von der IDE nachdenken und den Fehler beheben. `codecs.open()` benutzt heute niemand mehr, das kann `open()` schon lange selbst. Auch in Python 2 hätte man das schon nicht mehr verwendet, sondern `io.open()`.

Die Funktion verwendet eine Menge Namen die magisch aus dem Nichts kommen und die es so alle gar nicht geben dürfte, weil die dafür auf Modulebene als globale Variablen existieren müssen. Funktionen (und Methoden) bekommen alles was sie ausser Konstanten benötigen, als Argument(e) übergeben.

Dann bekommt die Funktion allerdings eine ganze Menge Argumente wo man überlegen sollte die zu einem Objekt zusammenzufassen. Bei GUIs kommt man nicht wirklich um objektorientierte Programmierung (OOP) herum, also eigene Datentypen in Form von Klassen zu schreiben.

`time_start` wird definiert, aber nicht verwendet. Da `s` auch nicht verwendet wird, nehme ich mal an die Funktion geht danach noch weiter → die ist jetzt schon ziemlich lang, und eine Funktion/Methode sollte möglichst nur eine Sache erledigen. Wenn es verschiedene Arten gibt ein Passwort zu erzeugen, wäre es übersichtlicher die allgemeine Funktion/Methode dafür das an verschiedene Funktionen zu delegieren. Da kann man dann ausserdem auch gleich die GUI von der Programmlogik trennen, und die Funktionen so schreiben, dass sie die tatsächlichen Argument übergeben bekommen und keine GUI-Elemente aus denen sie sich die Werte heraus holen müssen. Dann kann man die Funktionen leichter testen und auch wiederverwenden.

Viele Namen scheinen von Yoda zu stammen. Ein `entry_trennzeichen` ist ein Trennzeichen das Einträge voneinander trennt. Was wir hier eigentlich haben ist aber ein `trennzeichen_entry`, also ein Eingabefeld für Trennzeichen.

`word_list` wird ein bisschen zu früh definiert. Das wird ja erst nach dem kompletten einlesen gefüllt, müsste als erst danach im Code definiert werden. Hier ist das noch relativ nah dran, aber Code neigt ja dazu zu wachsen, und es passiert dann leicht, dass man Definitionen vergisst aus dem Code zu entfernen, wenn man Code ändert und die nicht mehr notwendig sind, wenn sie zu weit von ihrer Verwendung eingeführt werden. Diese Definition kann man sich nämlich sparen wenn man statt einer Schleife mit `random.choice()` einfach `random.sample()` verwenden würde.

Also eigentlich laut dem vorliegenden Code ja `random.choices()`, aber das fühlt sich falsch an, weil sich da, wie auch im aktuellen Code, Passwörter wiederholen können.

Das mit `var_zahl_am_ende` sieht falsch aus, also dass das an der falschen Stelle und an der falschen Zeichenkette ausgeführt wird.

Ungetestet:

Code: Alles auswählen

def passworte_auswaehlen(
    trennzeichen,
    mindestlaenge,
    wortanzahl,
    wortlistendateiname,
    zahl_am_ende,
):
    with open(wortlistendateiname, encoding="utf-8") as lines:
        alle_worte = (line.strip() for line in lines)
        worte = [wort for wort in alle_worte if len(wort) >= mindestlaenge]

    ergebnis_text = trennzeichen.join(
        random.sample(worte, min(wortanzahl, len(worte)))
    )
    return (
        f"{ergebnis_text}{random.randint(0, 9)}"
        if zahl_am_ende
        else ergebnis_text
    )


def passwort_erzeugen(
    erzeugtes_passwort_label,
    erzeugtes_passwort_entry,
    passwortart_notebook,
    trennzeichen_entry,
    wortmindestlaenge_spinbox,
    wortanzahl_spinbox,
    wortlistendateiname_entry,
    zahl_am_ende_var,
):
    erzeugtes_passwort_label.configure(bg="SystemButtonFace")
    erzeugtes_passwort_entry.configure(bg="white")
    try:
        if passwortart_notebook.index(tk.CURRENT) == 1:
            ergebnis = passworte_auswaehlen(
                trennzeichen_entry.get(),
                int(wortmindestlaenge_spinbox.get()),
                int(wortanzahl_spinbox.get()),
                wortlistendateiname_entry.get(),
                zahl_am_ende_var.get(),
            )
        ...

    except Exception as error:
        messagebox.showerror("Fehler", error)
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
steff01
User
Beiträge: 2
Registriert: Sonntag 2. Oktober 2022, 07:40

wow, so viele tolle Antowrten.
Danke Euch, ich "arbeite" das durch
Antworten