While-Schleife, Abfrage der Bedingung außerhalb der 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
PatrickF
User
Beiträge: 27
Registriert: Sonntag 1. Mai 2022, 09:43

Hallo zusammen,

sitze an einer Aufgabe aus einem Buch, ein kleines Ratespiel:

Code: Alles auswählen

import random as rd
import sys
while True:
    wahl=rd.choice(["k", "z"])
    antwort=input("Kopf oder Zahl (k/z), e für Ende: ")
    while antwort!="k" and antwort!="z" and antwort!="e":
        antwort=input("Kopf oder Zahl (k/z), e für Ende: ")
        print("Fehler")
    continue   
    if antwort=="e":
        print("Vielen Dank für das Spiel!")
        sys.exit()
    if antwort==wahl:
        print("Gewonnen")
        if wahl=="k":
            print("Ich habe auch Kopf gewählt\n")
        elif wahl=="z":
            print("Ich habe auch Zahl gewählt\n")
        
    else:
        print("Pech!")
        if wahl=="k":
            print("Ich habe Kopf gewählt\n")
        elif wahl=="z":
            print("Ich habe Zahl gewählt\n")


Irgendwie verstehe ich das Prinzip der While-Schleife noch nicht, ich möchte ja alle Eingaben außer "k,z,e" abfagen, aber wie mache ich das innerhalb der Schleife, wenn die Wahl der Option außerhalb erfolgt.

Entweder die Schleife läuft endlos, oder ich bekomme eine Fehlermeldung:

Code: Alles auswählen

NameError: name 'antwort' is not defined
wenn ich die Variable "antwort" nicht vorher zuweise (Zeile 5).

Komme einfach nicht weiter...
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@PatrickF: Die Wahl der Optionen darf halt nicht ausserhalb erfolgen, wenn diese Wahl das ist was (potentiell) wiederholt werden soll, denn was wiederholt werden soll muss ja in der Schleife stehen, dafür ist sie ja da.

Was man so nicht macht ist Code vor der Schleife zu wiederholen der dann auch noch mal in der Schleife steht. Du willst die Bedingung nicht ausserhalb der Schleife auswerten, sondern wirklich *in* der Schleife, im Schleifenkörper und nicht als Schleifenbedingung. Denn was Du haben willst ist eine nachprüfende Schleife, also eine wo erst am Ende die Abbruchbedingung geprüft wird. Das sind in Python Endlosschleifen, also ``while True:`` wo in der Schleife mit ``if`` geprüft und gegebenenfalls mit ``break`` die Schleife verlassen wird. Man kann auch, wo es Sinn macht, dann auch noch Code nach dem Abbruch schreiben, für den Fall dass dieser Fall nicht genommen wird.

Weitere Anmerkungen: Verwende keine kryptischen Abkürzungen als Namen. `random` sollte man nicht `rd` abkürzen. Das macht das Programm einfach nur unnötig schwerer zu lesen und spart hier noch nicht einmal Tastendrücke. Was an sich kein Wert ist, denn Quelltext wird deutlich öfter gelesen als geschrieben, also sollte man mehr Wert auf Lesbarkeit als auf wenig tippen legen.

Statt `antwort` in drei Teilbedingungen gegen einzelne Werte zu testen, kann man auch testen ob `antwort` in einer Sequenz von Werten vorkommt.

Das ``continue`` macht an der Stelle keinen Sinn weil es zu unerreichbarem Code führt. Generell sollte man mit ``continue`` eher sparsam umgehen, weil das ein Sprungbefehl ist, den man nicht an der Einrückung ablesen kann. Das kann überraschend für den Leser sein und Probleme beim Refactoring machen.

`sys.exit()` sollte man nur verwenden wenn man wenigstens potentiell einen anderen Rückgabecode als 0 an den Aufrufer des Programms übermitteln will. Das ist kein Ersatz oder Notausgang für Fälle wo man sich um einen sauberen Weg durch den Programmablauf drücken möchte.

Man kann sich etwas Code und Daten(teil)wiederholungen sparen wenn man die Wahl dynamisch in den Text hineinformatiert, statt das mit ``if``/``else`` auszuprogrammieren.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import random


def main():
    while True:
        while True:
            antwort = input("Kopf oder Zahl (k/z), e für Ende: ")
            if antwort in ["k", "z", "e"]:
                break
            print("Fehler")

        if antwort == "e":
            print("Vielen Dank für das Spiel!")
            break

        wahl = random.choice(["Kopf", "Zahl"])
        if antwort == wahl[0].lower():
            print("Gewonnen")
            print(f"Ich habe auch {wahl} gewählt\n")
        else:
            print("Pech!")
            print(f"Ich habe {wahl} gewählt\n")


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
PatrickF
User
Beiträge: 27
Registriert: Sonntag 1. Mai 2022, 09:43

Danke erstmal für die ausführliche Antwort. Da ich ja "auf dem Lernpfad" bin fehlen natürlich noch viele Dinge, die dann später hoffentlich dazu kommen.

Noch ein paar Erläuterungen warum ich was gemacht habe:

Das "random as rd" habe ich einfach gemacht weil das z.B. ja auch bei numpy so eine Art Standard ist (import numpy as np), aber hier kann man natürlich bei "random" bleiben.

Das "sys.exit() habe ich von "Automate the boring stuff", gefällt mir auch nicht wirklich weil man das ja eigens importierten muss.

Das "continue" wollte ich mal ausprobieren weil ich auch gerade hier lese, da geht es auch um while-Schleifen:

Code: Alles auswählen

while True: 
    zahl = int(input("Geben Sie eine Zahl ein: "))
    if zahl < 0: 
        print("Negative Zahlen sind nicht erlaubt")
        continue
    ergebnis = 1
    while zahl > 0: 
        ergebnis = ergebnis * zahl
        zahl = zahl - 1
    print("Ergebnis: ", ergebnis)
Deshalb schien mir das "continue" sinnvoll, um alles außer den gewünschten Tasten abzufangen. Das "while True und dann 'if'" muss ich mir noch verinnerlichen... :D
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@PatrickF: Wie gesagt würde ich ``continue`` meiden wo es geht, beziehungsweise kann man das eigentlich immer vermeiden, also braucht es schon einen guten Grund warum es besser ist diese Abkürzung zu verwenden, als es ”sauber” zu lösen.

Also beim Beispiel mit der Fakultät einfach so:

Code: Alles auswählen

while True: 
    zahl = int(input("Geben Sie eine Zahl ein: "))
    if zahl < 0: 
        print("Negative Zahlen sind nicht erlaubt")
    else:        
        ergebnis = 1
        while zahl > 0: 
            ergebnis *= zahl
            zahl -= 1
        
        print("Ergebnis:", ergebnis)
Die innere ``while``-Schleife geht hier auch nur weil das in dem Text vor der ``for``-Schleife erklärt wird, weil das ist eigentlich ein Fall für eine ``for``-Schleife:

Code: Alles auswählen

        ergebnis = 1
        for i in range(1, zahl + 1):
            ergebnis *= i
Man könnte es funktional auch ohne Schleife ausdrücken:

Code: Alles auswählen

from functools import reduce
from operator import mul as multiply

        ...
        
        ergebnis = reduce(multiply, range(1, zahl + 1), 1)
Oder natürlich gleich mit `math.factorial()`:

Code: Alles auswählen

from math import factorial

        ...
        
        print("Ergebnis:", factorial(zahl))
Dann möchte man vielleicht auch noch daraf reagieren wenn der Benutzer gar keine Zahl eingibt, sondern nichts oder andere Zeichen als Ziffern und auch wenn er Strg-C drückt um das Programm abzubrechen:

Code: Alles auswählen

#!/usr/bin/env python3
from math import factorial


def main():
    try:
        while True:
            try:
                zahl = int(input("Geben Sie eine Zahl ein: "))
            except ValueError:
                print("Das war keine Zahl!")
            else:
                if zahl < 0:
                    print("Negative Zahlen sind nicht erlaubt")
                else:
                    print("Ergebnis:", factorial(zahl))

    except KeyboardInterrupt:
        print("Und Tschüss.")


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1123
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,
__blackjack__ hat geschrieben: Mittwoch 18. Mai 2022, 15:07 @PatrickF: Wie gesagt würde ich ``continue`` meiden wo es geht, beziehungsweise kann man das eigentlich immer vermeiden, also braucht es schon einen guten Grund warum es besser ist diese Abkürzung zu verwenden, als es ”sauber” zu lösen.
Könntest du bitte erklären, warum man 'continue' meiden sollte? Liegt es daran, dass das das Lesen des Codes erschweren kann, bzw. der Lesefluss gestört wird oder gibt es noch einen technischen Grund? Und falls du noch ein paar Zeilen übrig hast, was wäre denn ein guter Grund?


Danke und sorry für die Zwischenfrage.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
PatrickF
User
Beiträge: 27
Registriert: Sonntag 1. Mai 2022, 09:43

__blackjack__ hat geschrieben: Mittwoch 18. Mai 2022, 15:07 @PatrickF: Wie gesagt würde ich ``continue`` meiden wo es geht, beziehungsweise kann man das eigentlich immer vermeiden, also braucht es schon einen guten Grund warum es besser ist diese Abkürzung zu verwenden, als es ”sauber” zu lösen.

..
Oder natürlich gleich mit `math.factorial()`:

Code: Alles auswählen

from math import factorial

        ...
        
        print("Ergebnis:", factorial(zahl))
..
Ok, aber das mache ich ja bewusst nicht weil ich da einen Lerneffekt habe wenn ich das alles mit möglichst wenig Sonderfunktionen und am besten nur mit Grundrechenarten mache. Diese ganzen Funktionen werde ich mir hoffentlich im Laufe der Zeit aneignen, möchte ja mit Python hauptsächlich etwas 'Mathe basteln'...
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Einen technischen Grund gibt es nicht. Mich stört's beim Lesen, weil man diesen Sprung nicht an der Einrückung ablesen kann, man kann das aber in der Regel ganz einfach mit einem ``else`` zu dem ``if`` in dem das ``continue`` steht ersetzen. Wenn man ``continue`` verwendet, hat man sich die Möglichkeit verbaut etwas am Ende jedes Schleifendurchlaufs zu machen, ohne dass man das auch bei jedem ``continue`` zusätzlich noch mal hinschreiben muss. Und man kann den Schleifenkörper oder Teile davon die ein ``continue`` enthalten nicht einfach in eine Funktion oder Methode herausziehen.

Wenn ``continue`` am Anfang einer Schleife so ähnlich wie ein „early exit“ verwendet wird ist es IMHO okay, wobei ich persönlich auch da nicht den Wunsch verspüre eine Einrückebene zu ”sparen”, aber mitten in längerem Schleifen-Code versteckt, muss man wissen, dass es da ist, sonst kann man leicht(er) übersehen unter welchen Umständen Code danach/gegen Ende der Schleife *nicht* ausgeführt wird.

@PatrickF: Das war mir schon klar, deswegen hatte ich in dem Beitrag ja auch den Satz „Die innere ``while``-Schleife geht hier auch nur weil das in dem Text vor der ``for``-Schleife erklärt wird, weil das ist eigentlich ein Fall für eine ``for``-Schleife:“. Ich wollte halt auch zeigen wie man es ”ernsthaft” umsetzen würde, inklusive Zwischenschritte dort hin, also `reduce()` falls es `math.factorial()` nicht geben würde, beziehungsweise ``for``-Schleife falls es `reduce()` nicht geben würde, und zu Deinem ``while`` was man nehmen müsste falls es ``for`` nicht geben würde.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1123
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die Erklärung @__blackjack__ 👍


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten