Mehrere if Abfragen mit input in einer while Schleife

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
superdario99
User
Beiträge: 7
Registriert: Freitag 12. August 2022, 10:24

Moin,

ich stehe vor einem Problem. Ich schreibe an einem kleinen Quiz (Macht irgendwie jeder Anfänger am Anfang oder? Eignet sich aber auch ganz gut finde ich)
und bin im Prinzip fertig und bei meinem letzten Input, wo ich die Wahl aus 3 Möglichkeiten habe.

"Fehler" - Damit lasse ich mir die Anzahl der gemachten Fehler ausgeben
"Ende" - Damit beende ich das Programm
"Again" - Damit startet das Quiz von neu.

Alle eingaben funktionieren soweit. Ich möchte jedoch, wenn ich mir z.B meine Fehler ausgeben lasse im Anschluss die Möglichkeit haben, zu beenden oder von vorne zu beginnen.
Beide eingaben bringen mich jedoch wieder zum Anfang der if Abfrage

Code: Alles auswählen

    while True:
        end_quiz = input("""Text""")
        if end_quiz == "Fehler" and fehler_zahl == 0:
            end_quiz = True
            input(f"""Quiz beenden: 'Ende'
Quiz wiederholen: 'again'""")
        else:

            end_quiz = input(f"""Quiz beenden: 'Ende'
Quiz wiederholen: 'again'""")
            break
        if end_quiz == "again":
            break
        if end_quiz == "Ende":
            print("Bis zum nächsten mal!")
            time.sleep(1)
            print("...")
            time.sleep(2)
            exit()
Hab den Text mal ein bisschen gekürzt. Hab bei

Code: Alles auswählen

if end_quiz == "Fehler":
lediglich 2 unterschiedliche Ausgaben geschrieben. Je nachdem ob 0 Fehler, oder mehr.

Hoffe ihr versteht was ich meine und könnt mir helfen.

Lg,
Dario
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Um den Code zu verstehen, mußt Du ihn so lesen, wie Du in geschrieben hast, und nicht so, wie Du wünschst, dass er funktioniert.
Du fragst also als erstes nach """Text""" und reagierst auf die Eingabe in zwei Fälle. Fall 1, Eingabe ist "Fehler" und fehler_zahl ist 0, dann wird end_quiz auf True gestellt und weitergemacht, im andren Fall, wird die Schleife beendet.
Da end_quiz weder "again" noch "Ende" ist, fängt die Schleife wieder von vorne an.
Der Code läßt sich also vereinfachen zu

Code: Alles auswählen

    while True:
        end_quiz = input("""Text""")
        if end_quiz != "Fehler" or fehler_zahl != 0:
            break
        input("Quiz beenden: 'Ende'\n"
            "Quiz wiederholen: 'again'")
    end_quiz = input("Quiz beenden: 'Ende'\n"
        "Quiz wiederholen: 'again'")
Wahrscheinlich ist es nicht das, was Du möchtest, zumal die Eingaben nicht weiter verwendet werden.
Variablen sollte man nicht für verschiedene Zwecke wiederverwenden, weil es Fehler leicht verdecken kann. `end_quiz` sollte also nur einmal per `input` benutzt werden.
`exit` hat in einem Ordentlichen Programm nichts verloren, Ein Programm endet, indem es die `main`-Funktion verläßt.
Für Eingaben sollte man eine eigene Funktion schreiben, damit Eingabe und Verarbeitung nicht so durchmischt werden.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@superdario99: Man sollte nicht verschiedene Typen innerhalb eines Namensraums an den gleichen Namen binden. Das `end_quiz` meistens eine Zeichenkette, an einer Stelle in der Funktion aber ein Wahrheitswert ist, verwirrt den Leser. Wobei der Wahrheitswert auch nirgends verwendet wird. Die Zuweisung kann man also ersatzlos streichen.

f-Zeichenketten machen ohne formatierte Werte keinen Sinn.

Mehrzeilige Zeichenketten mitten in Code machen das Programm schwer lesbar, weil die die Einrückung kaputt machen an der man normalerweise relativ einfach die Programmstruktur erkennen kann.

Sinnloses `sleep()` nervt nur die Benutzer.

`exit()` sollte man nicht als ”Notausgang” verwenden weil man sich um einen sauberen Programmablauf kümmern will. Die Funktion sollte man nur einsetzen wenn man mindestens potentiell einen anderen Rückgabecode als 0 an das aufrufende Programm kommunizieren will. *Dafür* ist die nämlich gedacht.

`end_quiz` ist nicht so ein wirklich toller Name für eine Antwort vom Benutzer die verschiedenste Dinge auslösen kann.

Zwischenstand:

Code: Alles auswählen

def do_quiz():
    fehler_zahl = 0

    while True:
        answer = input("Text")
        if answer == "Fehler" and fehler_zahl == 0:
            input("Quiz beenden: 'Ende'\nQuiz wiederholen: 'again'")
        else:
            answer = input("Quiz beenden: 'Ende'\nQuiz wiederholen: 'again'")
            break

        if answer == "again":
            break

        if answer == "Ende":
            print("Bis zum nächsten mal!")
            return
Die Programmlogik macht überhaupt keinen Sinn. Im ersten ``if`` wird das Ergebnis der Frage nirgends gespeichert. Im ``else`` steht die *selbe* Frage, die Schleife wird dann aber verlassen bevor die Antwort ausgewertet wird. Die selbe Frage sollte man im Code nicht wiederholen. Code und Daten wiederholen vermeidet man als Programmierer in der Regel. Das ist Mehrarbeit und fehleranfällig bei Änderungen, weil man immer daran denken muss alle Kopien zu ändern, und auch alle gleich oder zumindest gleichwertig.

Ich wäre mit Gross-/Kleinschreibung der Antworten grosszügiger. Benutzer werden Dich sonst irgendwann verfluchen das sie „again“ klein schreiben, „Ende“ aber gross schreiben müssen. Das „denglisch“ ist auch nicht schön. Entweder sollte man auf Deutsch mit dem Benutzer kommunizieren (und der mit dem Programm) oder auf Englisch; oder irgendeine andere Sprache, aber halt konsistent immer die Gleiche.

Man könnte auch noch den Fall behandeln, falls der Benutzer etwas unbekanntes eingibt.

Code: Alles auswählen

def main():
    while True:
        fehler_zahl = 0

        # Quiz code goes here.

        while True:
            answer = input("Text").lower()
            if answer == "fehler":
                if fehler_zahl == 0:
                    print("Perfekt! Keine Fehler!")
                else:
                    print(f"{fehler_zahl} Fehler! Das war noch nicht perfekt.")

            elif answer == "nochmal":
                break

            elif answer == "ende":
                print("Bis zum nächsten mal!")
                return

            else:
                print(f"Unbekannte Eingabe: {answer!r}")
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
superdario99
User
Beiträge: 7
Registriert: Freitag 12. August 2022, 10:24

Danke euch beiden für die ausführlichen Antworten.
Ich muss mich da erstmal richtig reinfuchsen und probiere dann die genannten Fehler und Verbesserungen umzusetzen.
Vieles ist aktuell auch denke ich einfach noch ein Problem der Verständnis oder eher gesagt verstehe ich oftmals die Logik dahinter noch nicht so.

Aber ich werds im laufe des Abends und morgen früh durcharbeiten und mich dann wieder melden.

Vielen dank nochmal!
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Ergänzend zu blackjacks Antwort habe ich hier seine Programmstruktur mit Hilfe von match-case (siehe: PEP 636 - Structural Pattern Matching) abgebildet. Das hat meines Erachtens den Vorteil, dass nicht jedesmal explizit ein Vergleich mit answer explizit geschrieben werden muss und so die Intention klarer ist. Der Nachteil ist jedoch eine weitere Einrückungsebene, welche mir den case "fehler" etwas schwerer erfassen lässt.

Code: Alles auswählen

def main() -> None:
    while True:
        fehler_zahl: int = 0

        # Quiz code goes here.

        while True:
            match input("Text").lower():
                case "fehler":
                    if fehler_zahl == 0:
                        print("Perfekt! Keine Fehler!")
                    else:
                        print(f"{fehler_zahl} Fehler! Das war noch nicht perfekt.")
                case "nochmal":
                    break
                case "ende":
                    print("Bis zum nächsten mal!")
                    return
                case _ as answer:
                    print(f"Unbekannte Eingabe: {answer!r}")

if __name__ == '__main__':
    main()
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@bwbg: Also wenn es nach mir ginge bräuchte es Typannotation ja gar nicht, aber wenn man welche macht, sollte man sich wirklich so redundanten, überflüssigen Kram wie ``fehler_zahl: int = 0`` sparen. Das 0 eine ganze Zahl ist, sehen sowohl menschliche Leser als auch die verschiedenen Werkzeuge zum prüfen von Typen schon ohne die Typannotation. Das ist wie ein Kommentar der einfach nur noch mal beschreibt, was da auch ohne Kommentar schon offensichtlich zu lesen ist.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

(OT)
@__backjack__: Du hast noch nie einen Hehl daraus gemacht, dass du Typannotationen nicht brauchst 8). Zusammen mit z. B. mypy empfinde ich sie meistens jedoch als recht hilfreich; ganz besonders mit den Typen aus collections.abc. Gerade weil mir das Auszeichnen einen zusätzlichen Gedankenschritt im Sinne von "was benötige/erwarte ich hier eigentlich" abringt und ich es hiermit direkt dokumentiert habe.

Und ja, die Annotation von fehler_zahl ist hier definitiv überflüssig.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
superdario99
User
Beiträge: 7
Registriert: Freitag 12. August 2022, 10:24

So ich habe mal versucht die ganzen Anmerkungen umzusetzen und habe selber auch nochmal ein bisschen rumprobiert, jetzt läuft alles, also schon mal vielen Dank!

Meine Schleifen, wo der User die Fragen beantwortet sehen jetzt folgendermaßen aus

Code: Alles auswählen

while True:
		frage_1 = input("""Frage - 1""")
            	if frage_1 == "d":
                	input("""Antwort""")
                	break
            	else:
                	print(bcolors.red + "Das war falsch, probiere es erneut." + bcolors.reset)
                	fehler_zahl = fehler_zahl + 1
Und das Ende habe ich dann so gelöst:

Code: Alles auswählen

while True:
	end_quiz = input("""(...) Fehler, Nochmal, Ende""")
            if end_quiz == "Fehler":
                if fehler_zahl == 0:
                    print(f"Du hast " + bcolors.green + f" {fehler_zahl} " + bcolors.reset + " Fehler gemacht, super!")
                elif fehler_zahl in range (0, 6):
                    print(f"Du hast " + bcolors.yellow + f" {fehler_zahl} " + bcolors.reset + " Fehler gemacht. Das geht besser!")
                else:
                    print(f"Du hast " + bcolors.red + f" {fehler_zahl} " + bcolors.reset + " Fehler gemacht. Das probieren wir aber gleich nochmal!")
            elif end_quiz == "Nochmal":
                break
            elif end_quiz == "Ende":
                return
            else:
                print(bcolors.red + "Unbekannte Antwort" + bcolors.reset)
Aber nur weils jetzt läuft, muss das ja noch nicht zu 100% richtig / gut gelöst sein. Habt ihr noch Anmerkungen dazu, irgendwas was ich definitiv anders schreiben sollte etc.?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@superdario99: Gute Namen sind wichtig. Das `end_quiz` kein so guter Name ist, hatte ich ja schon erwähnt, aber auch `frage_1` ist schlecht. Erstens ist das gar keine Frage, denn der Wert steht für genau das Gegenteil: eine Antwort. Zweitens nummeriert man keine Namen. Dann will man entweder gar keine einzelnen Namen und Werte, sondern eine Datenstruktur (oft eine Liste), oder man sollte sich bessere Namen überlegen.

Hier vermute ich gar keine Nummer sondern einfach nur `antwort`, denn sehr wahrscheinlich braucht man diesen Wert nach einer weiteren Antwort nie wieder, also muss man auch nicht jede Antwort an einen unterschiedlichen Namen binden. Dafür vermute ich wäre eine Datenstruktur für die Fragen sinnvoll. Das sieht jetzt so aus, als wenn es da für jede Frage/Antwort eigenen Code gibt. Das wäre besser eine Schleife über die Fragen und richtigen Antworten. Das ist flexibler, weil man dann den gleichen Code für verschiedene Quizthemen verwenden kann. Man kann die Quizdaten dann auch ausserhalb speichern, beispielsweise in CSV-Dateien.

Die `bcolors`-Attribute sind Zeichenketten, die kann man also auch einfach wie andere Werte in f-Zeichenketten formatieren, statt das mit ``+`` zu mischen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
superdario99
User
Beiträge: 7
Registriert: Freitag 12. August 2022, 10:24

__blackjack__ hat geschrieben: Donnerstag 18. August 2022, 14:19 @superdario99: Gute Namen sind wichtig. Das `end_quiz` kein so guter Name ist, hatte ich ja schon erwähnt, aber auch `frage_1` ist schlecht. Erstens ist das gar keine Frage, denn der Wert steht für genau das Gegenteil: eine Antwort. Zweitens nummeriert man keine Namen. Dann will man entweder gar keine einzelnen Namen und Werte, sondern eine Datenstruktur (oft eine Liste), oder man sollte sich bessere Namen überlegen.

Hier vermute ich gar keine Nummer sondern einfach nur `antwort`, denn sehr wahrscheinlich braucht man diesen Wert nach einer weiteren Antwort nie wieder, also muss man auch nicht jede Antwort an einen unterschiedlichen Namen binden. Dafür vermute ich wäre eine Datenstruktur für die Fragen sinnvoll. Das sieht jetzt so aus, als wenn es da für jede Frage/Antwort eigenen Code gibt. Das wäre besser eine Schleife über die Fragen und richtigen Antworten. Das ist flexibler, weil man dann den gleichen Code für verschiedene Quizthemen verwenden kann. Man kann die Quizdaten dann auch ausserhalb speichern, beispielsweise in CSV-Dateien.

Die `bcolors`-Attribute sind Zeichenketten, die kann man also auch einfach wie andere Werte in f-Zeichenketten formatieren, statt das mit ``+`` zu mischen.
Okay, also um das alles nochmal runterzubrechen, in der Hoffnung ich hab das alles soweit verstanden.
Das mit den Namen für die Variablen hab ich mir zu Herzen genommen, dachte mir nur ich schreib sowieso das ganze Quiz nochmal neu, dann setze ich es erst da um.

Im nachhinein betrachtet hast du recht, ein einfaches "antwort" reicht ja eigentlich.. Habe ich ebenfalls umgesetzt.

Mit Datenstruktur meinst du in diesem Fall, dass ich einfach eine Liste erstelle mit allen Fragen und die am Ende dann einfach in den if's abfrage?

Und wie meinst du
Das wäre besser eine Schleife über die Fragen und richtigen Antworten
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Eine Liste nur mit den Fragen reicht nicht, man braucht zum prüfen ja auch die richtige Antwort zu jeder Frage. Also zum Beispiel eine Liste mit Tupeln aus Frage und richtiger Antwort. Also beispielsweise so etwas in dieser Richtung:

Code: Alles auswählen

    for frage, richtige_antwort in [("Hauptstadt von Deutschland?", "Berlin"), ("Beste Programmiersprache?", "Python"), …]:
        # Hier dann die Frage stellen, die Antwort entgegennehmen, und mit der richtigen Antwort vergleichen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
harryberlin
User
Beiträge: 227
Registriert: Donnerstag 17. Dezember 2015, 12:17

__blackjack__ hat geschrieben: Mittwoch 17. August 2022, 15:27 @superdario99: Man sollte nicht verschiedene Typen innerhalb eines Namensraums an den gleichen Namen binden. Das `end_quiz` meistens eine Zeichenkette, an einer Stelle in der Funktion aber ein Wahrheitswert ist, verwirrt den Leser. Wobei der Wahrheitswert auch nirgends verwendet wird. Die Zuweisung kann man also ersatzlos streichen.

f-Zeichenketten machen ohne formatierte Werte keinen Sinn.

Mehrzeilige Zeichenketten mitten in Code machen das Programm schwer lesbar, weil die die Einrückung kaputt machen an der man normalerweise relativ einfach die Programmstruktur erkennen kann.

Sinnloses `sleep()` nervt nur die Benutzer.

`exit()` sollte man nicht als ”Notausgang” verwenden weil man sich um einen sauberen Programmablauf kümmern will. Die Funktion sollte man nur einsetzen wenn man mindestens potentiell einen anderen Rückgabecode als 0 an das aufrufende Programm kommunizieren will. *Dafür* ist die nämlich gedacht.

`end_quiz` ist nicht so ein wirklich toller Name für eine Antwort vom Benutzer die verschiedenste Dinge auslösen kann.

Zwischenstand:

Code: Alles auswählen

def do_quiz():
    fehler_zahl = 0

    while True:
        answer = input("Text")
        if answer == "Fehler" and fehler_zahl == 0:
            input("Quiz beenden: 'Ende'\nQuiz wiederholen: 'again'")
        else:
            answer = input("Quiz beenden: 'Ende'\nQuiz wiederholen: 'again'")
            break

        if answer == "again":
            break

        if answer == "Ende":
            print("Bis zum nächsten mal!")
            return
Die Programmlogik macht überhaupt keinen Sinn. Im ersten ``if`` wird das Ergebnis der Frage nirgends gespeichert. Im ``else`` steht die *selbe* Frage, die Schleife wird dann aber verlassen bevor die Antwort ausgewertet wird. Die selbe Frage sollte man im Code nicht wiederholen. Code und Daten wiederholen vermeidet man als Programmierer in der Regel. Das ist Mehrarbeit und fehleranfällig bei Änderungen, weil man immer daran denken muss alle Kopien zu ändern, und auch alle gleich oder zumindest gleichwertig.

Ich wäre mit Gross-/Kleinschreibung der Antworten grosszügiger. Benutzer werden Dich sonst irgendwann verfluchen das sie „again“ klein schreiben, „Ende“ aber gross schreiben müssen. Das „denglisch“ ist auch nicht schön. Entweder sollte man auf Deutsch mit dem Benutzer kommunizieren (und der mit dem Programm) oder auf Englisch; oder irgendeine andere Sprache, aber halt konsistent immer die Gleiche.

Man könnte auch noch den Fall behandeln, falls der Benutzer etwas unbekanntes eingibt.

Code: Alles auswählen

def main():
    while True:
        fehler_zahl = 0

        # Quiz code goes here.

        while True:
            answer = input("Text").lower()
            if answer == "fehler":
                if fehler_zahl == 0:
                    print("Perfekt! Keine Fehler!")
                else:
                    print(f"{fehler_zahl} Fehler! Das war noch nicht perfekt.")

            elif answer == "nochmal":
                break

            elif answer == "ende":
                print("Bis zum nächsten mal!")
                return

            else:
                print(f"Unbekannte Eingabe: {answer!r}")
kann man nicht für den Fall "nochmal" das continue nehmen, statt break?
Fehler entsprechend davor auf 0 setzen.
empty Sig
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast zwei ineinander verschachtelte Schleifen: die äußere Schleife umfasst ein Spiel, die innere nur die Abfrage bis ein gültiges Commando eingegeben worden ist. Wenn Du ein neues Spiel starten willst, mußt Du die innere Schleife per `break` verlassen.

Übersichtlicher wird es, wenn man mehrere Funktionen definiert:

Code: Alles auswählen

def do_quiz():
    error_count = 0
    # Quiz code goes here.
    return error_count

def ask_restart(error_count):
    while True:
        answer = input("Text").lower()
        if answer == "fehler":
            if error_count == 0:
                print("Perfekt! Keine Fehler!")
            else:
                print(f"{error_count} Fehler! Das war noch nicht perfekt.")

        elif answer == "nochmal":
            return True

        elif answer == "ende":
            return False

        else:
            print(f"Unbekannte Eingabe: {answer!r}")

def main():
    while True:
        error_count = do_quiz()
        if not ask_restart(error_count):
            break
    print("Bis zum nächsten mal!")

if __name__ == "__main__":
    main()
Antworten