Beenden von try - except - else

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
merry
User
Beiträge: 3
Registriert: Donnerstag 6. Oktober 2022, 02:03

Hi zusammen,

ich bin blutiger Neuling. Ich habe einen kleinen Addierer geschrieben (Python 3.10) bei dem ich Benutzereingaben, die keine Zahl sind abfangen möchte (try-except habe ich aus diesem Forum, vielen Dank dafür!), der zunächst auch funktioniert hat. Beim Versuch, den Code allerdings um ein paar Zeilen zu kürzen stoße ich bei dieser Version auf folgendes Problem:

Code: Alles auswählen

value_1 = input('Gebe die erste Zahl ein: \n')
while True:
    try:
        value_1 = int(value_1)
    except ValueError:
        value_1 = input('Du musst eine ganze Zahl eingeben. Versuche es erneut: \n')
    else:
        value_2 = input('Gebe die zweite Zahl ein: \n')
        while True:
            try:
                value_2 = int(value_2)
            except ValueError:
                value_2 = input('Du musst eine ganze Zahl eingeben. Versuche es erneut: \n')
            else:
                print("Die Addition deiner eingegebenen Zahlen ergibt: ")
                print(value_1 + value_2)
Die letzten beiden print() Anweisungen werden unendlich ausgeführt. Ich habe das zwar lösen können, indem ich unter dem letzten print() ein exit() eingefügt habe, allerdings verstehe ich nicht, warum diese beiden print() Anweisungen nicht nur einmal ausgeführt werden. In oberen while Block wird die else Anweisung von try-except ja auch nur solange ausgeführt, bis die Benutzereingabe eine Zahl ist.

Ich habe schon mehrere Varianten ausprobiert, z.B. mit einer if Bedingung. In diesem Fall komme ich aber nicht weiter als dem ersten except ValueError, der nur einmal ausgeführt wird. Anschließend wird das Programm beendet.

Ich hoffe nur, dass es sehr kompliziert ist und kein einfacher Logikfehler, sonst wäre ich enttäuscht :D

Hat jemand einen Tipp für mich?
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@merry: Du könntest die ``while``-Schleife mit ``break`` verlassen, dann bekommst Du aber das gleiche Problem mit der äusseren ``while``-Schleife, die Du auch im ``else`` mit ``break`` verlassen musst. Das geht zwar, aber das ist alles ein bisschen unübersichtlich. Als ersten Schritt würde man in das ``else`` bei beiden ``try``/``except`` einfach nur ein ``break`` schreiben um die unnötige Verschachtelung los zu werden, denn eigentlich sollen da ja drei Dinge nacheinander passieren: Eingabe erste Zahl, Eingabe zweite Zahl, berechnen und Ausgabe. Das wird aus dem verschachtelten Code nicht so wirklich deutlich. Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3


def main():
    value_1 = input("Gebe die erste Zahl ein:\n")
    while True:
        try:
            value_1 = int(value_1)
        except ValueError:
            value_1 = input(
                "Du musst eine ganze Zahl eingeben. Versuche es erneut:\n"
            )
        else:
            break

    value_2 = input("Gebe die zweite Zahl ein:\n")
    while True:
        try:
            value_2 = int(value_2)
        except ValueError:
            value_2 = input(
                "Du musst eine ganze Zahl eingeben. Versuche es erneut:\n"
            )
        else:
            break

    print("Die Addition deiner eingegebenen Zahlen ergibt:")
    print(value_1 + value_2)


if __name__ == "__main__":
    main()
Und hier sieht man jetzt sehr deutlich, das da zweimal fast der gleiche Code wiederholt wird. So etwas vermeiden Programmierer wo immer es geht mit Schleifen und/oder Funktionen, weil das unnötig Arbeit macht und fehleranfällig ist. Hier würde man sich eine Funktion schreiben, die vom Benutzer eine ganze Zahl abfragt. Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3


def ask_number(prompt):
    answer = input(prompt)
    while True:
        try:
            return int(answer)
        except ValueError:
            answer = input(
                "Du musst eine ganze Zahl eingeben. Versuche es erneut:\n"
            )


def main():
    value_1 = ask_number("Gebe die erste Zahl ein:\n")
    value_2 = ask_number("Gebe die zweite Zahl ein:\n")

    print("Die Addition deiner eingegebenen Zahlen ergibt:")
    print(value_1 + value_2)


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
karolus
User
Beiträge: 141
Registriert: Samstag 22. August 2009, 22:34

schöne Gelegenheit für eine rekursive Lösung:

Code: Alles auswählen

def ask_number(prompt):
    try:
        return int(input(prompt))
    except ValueError:
        return ask_number(
                "Du musst eine ganze Zahl eingeben. Versuche es erneut:\n"
            )
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Nicht wirklich, weil in Sprachen die keine „tail call optimitazion“ garantieren, Rekursion kein Ersatz für eine einfache Schleife sein sollte.

Code: Alles auswählen

def ask_number(prompt):
    while True:
        try:
            return int(input(prompt))
        except ValueError:
            prompt = "Du musst eine ganze Zahl eingeben. Versuche es erneut:\n"
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
merry
User
Beiträge: 3
Registriert: Donnerstag 6. Oktober 2022, 02:03

Hallo allerseits,

ich bin sicher, eure Antworten werden helfen. Da ich aber mit Teilen wie "prompt", "if __name__ == "__main__":", oder Begriffen wie rekursiv bzgl. Programmierung noch nichts anfangen kann, werde ich mal versuchen mich damit auseinanderzusetzen, um zu verstehen was dahintersteckt. Sonst wird das nur reines Abschreiben für mich.

Auf jeden Fall schon einmal vielen Dank für eure Antworten.
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@merry: `prompt` ist einfach ein Name. Bedeutet auf Deutsch „Aufforderung“/„Eingabeaufforderung“.

Das ``if __name__ == "__main__":``-Idiom wird hier in der Python-Dokumentation beschrieben: https://docs.python.org/3.8/library/__main__.html
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
hyle
User
Beiträge: 96
Registriert: Sonntag 22. Dezember 2019, 23:19
Wohnort: Leipzig

Ich habe da mal eine Frage. Wäre es in diesem Fall nicht sinnvoller nach answer.isdigit() zu fragen statt die schweren Geschütze aufzufahren, die das Skript nicht abstürzen lassen?

Bei Integers wäre das hier IMHO die bessere Variante, es sei denn es geht wirklich nur um das Verständnis zu try und except.
Alles was wir sind ist Sand im Wind Hoschi.
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

Mit dem Abfangen des ValueErrors fängst du das Problem ab.
Mit dem prüfen ob es Digits sind kannst du nicht ausschließen, dass die Umwandlung zu einem Into trotzdem schief geht.
Also ist das Fangen der Exception durchaus sinnvoll - und das Handeln von Exception in Python der "normale" Weg.

Code: Alles auswählen

>>> a = "𐩃"
>>> a.isdigit()
True
>>> int(a)
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    int(a)
ValueError: invalid literal for int() with base 10: '𐩃'
>>> 
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@hyle: Das funktioniert nicht, weil es Zeichen gibt die zwar bei `isdigit()` `True` liefern, die aber nicht mit `int()` umgewandelt werden können. Hier habe ich mal eine Liste erstellt: http://blog.marc.rintsch.de/2018/10/03/ ... t_dig.html

Es ist einfach am sichersten `int()` aufzurufen um heraus zu finden welchen Wert `int()` liefert und welche Eingaben es nicht mag, statt das mit anderen Mitteln vorher zu prüfen. Anders sieht es aus wenn man die Eingabe stärker einschränken will, beispielsweise auf die ASCII-Ziffern 0 bis 9, denn `int()` frisst auch alles Mögliche andere was Unicode in die Klasse Ziffern packt. Siehe den Blogbeitrag davor wo eine Liste mit allen Zeichen die `int()` in Zahlen umwandeln kann, erstellt wird. 🤓

Was idiomatisches Python angeht, gibt es für das Vorgehen auch schicke Abkürzungen EAFP https://docs.python.org/3.8/glossary.html#term-eafp und LBYL https://docs.python.org/3.8/glossary.html#term-lbyl
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
hyle
User
Beiträge: 96
Registriert: Sonntag 22. Dezember 2019, 23:19
Wohnort: Leipzig

Danke euch! Jetzt verstehe ich das "Problem" mit isdigit(), bzw. warum das nicht so geeignet ist.

Btw. Der Eintrag zu EAFP "Easier to ask for forgiveness than permission." hat was. :lol:
Alles was wir sind ist Sand im Wind Hoschi.
merry
User
Beiträge: 3
Registriert: Donnerstag 6. Oktober 2022, 02:03

__blackjack__ hat geschrieben: Donnerstag 6. Oktober 2022, 07:57 @merry: Du könntest die ``while``-Schleife mit ``break`` verlassen, dann bekommst Du aber das gleiche Problem mit der äusseren ``while``-Schleife, die Du auch im ``else`` mit ``break`` verlassen musst. Das geht zwar, aber das ist alles ein bisschen unübersichtlich. Als ersten Schritt würde man in das ``else`` bei beiden ``try``/``except`` einfach nur ein ``break`` schreiben um die unnötige Verschachtelung los zu werden, denn eigentlich sollen da ja drei Dinge nacheinander passieren: Eingabe erste Zahl, Eingabe zweite Zahl, berechnen und Ausgabe. Das wird aus dem verschachtelten Code nicht so wirklich deutlich. Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3


def main():
    value_1 = input("Gebe die erste Zahl ein:\n")
    while True:
        try:
            value_1 = int(value_1)
        except ValueError:
            value_1 = input(
                "Du musst eine ganze Zahl eingeben. Versuche es erneut:\n"
            )
        else:
            break

    value_2 = input("Gebe die zweite Zahl ein:\n")
    while True:
        try:
            value_2 = int(value_2)
        except ValueError:
            value_2 = input(
                "Du musst eine ganze Zahl eingeben. Versuche es erneut:\n"
            )
        else:
            break

    print("Die Addition deiner eingegebenen Zahlen ergibt:")
    print(value_1 + value_2)


if __name__ == "__main__":
    main()
Und hier sieht man jetzt sehr deutlich, das da zweimal fast der gleiche Code wiederholt wird. So etwas vermeiden Programmierer wo immer es geht mit Schleifen und/oder Funktionen, weil das unnötig Arbeit macht und fehleranfällig ist. Hier würde man sich eine Funktion schreiben, die vom Benutzer eine ganze Zahl abfragt. Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3


def ask_number(prompt):
    answer = input(prompt)
    while True:
        try:
            return int(answer)
        except ValueError:
            answer = input(
                "Du musst eine ganze Zahl eingeben. Versuche es erneut:\n"
            )


def main():
    value_1 = ask_number("Gebe die erste Zahl ein:\n")
    value_2 = ask_number("Gebe die zweite Zahl ein:\n")

    print("Die Addition deiner eingegebenen Zahlen ergibt:")
    print(value_1 + value_2)


if __name__ == "__main__":
    main()
Vielen Dank noch mal für diese Lösungen. Mir ist aufgefallen, dass deine erste Variante exakt meine allererste Lösung war, die ich versucht habe zu kürzen. Nur ohne es in eine Funktion gepackt zu haben.

Die zweite Lösung mit dem Abfangen innerhalb einer Funktion, gibt mir den Anreiz, generell mit Funktionen zu arbeiten.

Zu:

Code: Alles auswählen

if __name__ == "__main__":
habe ich mir ein paar Videos angeschaut. Das leuchtet ein.

Zu

Code: Alles auswählen

def ask_number(prompt):
    answer = input(prompt)
habe ich noch eine Frage:

Du schreibst:
@merry: `prompt` ist einfach ein Name. Bedeutet auf Deutsch „Aufforderung“/„Eingabeaufforderung“.
Ist dies ein Name, der frei gewählt werden kann, oder ein in Python fest definierter Begriff, den ich nutzen muss, wenn ich eine Eingabe vom Nutzer erwarte?

Und:

Code: Alles auswählen

 try:
        return int(input(prompt))
Was passiert hier genau bei diesem "return"?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

`prompt` ist ein Variabelnname, und Variablennamen kann man frei wählen. Dem Compiler ist es egal, ob das sinnvoll benannt ist, oder nicht. Du solltest einen Variablennamen so wählen, dass er gut verständlich ist, also aussagt, für was die Variable steht.
Bei `return` passiert immer das selbe. Die Funktion wird beendet und der Programmfluß kehrt zum Aufrufer zurück. Der Ausdruck, der hinter `return` steht, wird als Rückgabewert zurückgegeben.
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

__blackjack__ hat geschrieben: Freitag 7. Oktober 2022, 20:40 weil es Zeichen gibt die zwar bei `isdigit()` `True` liefern, die aber nicht mit `int()` umgewandelt werden können
Das ist spannend. Warum ist das so? Wirkt spontan wie ein Bug. Hattest du die Erkenntnisse weitergeleitet?
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Kebap: `isdigit()` prüft ob es laut Unicode eine Ziffer ist, während die Umwandlung mit `int()` das auf Zeichen einschränkt die laut Unicode *Dezimal*ziffern sind. Und da sind Ziffern ausgenommen, die nicht für die Darstellung von Ziffern im Dezimalsystem verwendet werden, und Ziffern die einen typographischen Aspekt haben, also Hoch-/Tiefstellung oder Kreis drum herum. Wäre auch irgendwie komisch wenn ``int("2³")`` als Ergebnis 23 liefern würde und nicht 8.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
karolus
User
Beiträge: 141
Registriert: Samstag 22. August 2009, 22:34

Hm. … kommt bei dir ne 8?

Code: Alles auswählen

ValueError                                Traceback (most recent call last)
Cell In [33], line 1
----> 1 int("2³")

ValueError: invalid literal for int() with base 10: '2³'
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

Nein. Wie __blackjack__ schrieb: die Umwandlung mit int() ist beschränkt auf Dezimalziffern.
karolus
User
Beiträge: 141
Registriert: Samstag 22. August 2009, 22:34

Na ja - ich hatte es nicht anders erwartet aber Blackjack schrieb auch recht eindeutig:
Wäre auch irgendwie komisch wenn ``int("2³")`` als Ergebnis 23 liefern würde und nicht 8.
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@karolus: So eindeutig ist der Satz nicht. Der komplette Satz bezieht sich auf den hypothetischen Fall, dass der Aufruf ein Ergebnis liefern, statt eine Ausnahme auslösen würde. Das „würde“ meint hier nicht nur die 23, sondern auch die 8.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten