Zum Inhalt
wannacode

Exceptions

Kurz gesagt: Eine Exception ist ein Objekt, das einen Fehler beschreibt, der während des Programmlaufs passiert ist: Der Traceback zeigt, wo, und try und except fangen ihn, damit das Programm weiterlaufen kann.

Problem

12a statt 12

Die Konzertkasse fragt, wie viele Tickets du willst. Ein Ticket kostet 15 €.

tickets.py
tickets = int(input("Wie viele Tickets? "))
print(f"Gesamt: {tickets * 15} €")

Mit der Eingabe 12 sagt das Programm „Gesamt: 180 €“. Aber der Kunde hat es eilig und tippt einen Buchstaben zu viel:

Die Konsole
Wie viele Tickets? 12a
Traceback (most recent call last):
File "/projects/tickets/tickets.py", line 1, in <module>
tickets = int(input("Wie viele Tickets? "))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: invalid literal for int() with base 10: '12a'

Das Programm hält an. Statt der Summe kommt ein langer Text, und die Zeile „Gesamt“ wird nie erreicht. Was bedeutet dieser Text, und wie verhinderst du, dass das Programm wegen eines Buchstabens anhält?

Die Idee

Exception

int() muss eine Zahl zurückgeben. Aus „12a“ kann es keine Zahl machen, und irgendeine Zahl zurückzugeben wäre gelogen. Deshalb erzeugt die Funktion ein Objekt, das das Problem beschreibt, und wirft es. Dieses Objekt heißt Exception (Ausnahme).

In Python heißt das Wort raise („erheben“), aber die Bedeutung ist dieselbe. Wenn niemand sie fängt, hält das Programm an und zeigt, was passiert ist. Dieser Text heißt Traceback, und du liest ihn von unten nach oben, weil das Neueste ganz unten steht (most recent call last):

  • Die letzte Zeile: die Art der Exception und die Meldung. ValueError: invalid literal for int() with base 10: '12a': ein falscher Wert, und hier ist der Text.
  • Die „File“-Zeilen darüber: wo es passiert ist. Das ist der Aufrufstapel aus der Lektion über Rekursion, aber von außen nach innen: Ganz oben ist die äußerste Stelle, und ganz unten ist die Funktion, die geworfen hat. <module> bedeutet den Code auf oberster Ebene der Datei.
  • Deine Zeile: die letzte „File“-Zeile, die auf deine Datei zeigt. Hier: tickets.py, line 1. Darunter steht die Zeile selbst, und die Zeichen ^^^ markieren den Teil, der geworfen hat.

Die Meldungen sind auf Englisch, weil die Sprache sie schreibt, nicht du.

Probier es aus

Finde den Fehler

Das Programm soll das letzte Ergebnis aus der Klassenarbeit ausgeben. Es hält damit an:

Fehler
Traceback (most recent call last):
File "/projects/tickets/scores.py", line 6, in <module>
print(last_score(scores))
^^^^^^^^^^^^^^^^^^
File "/projects/tickets/scores.py", line 2, in last_score
return scores[len(scores)]
~~~~~~^^^^^^^^^^^^^
IndexError: list index out of range

Lies den Traceback. In welcher Zeile des Programms steckt der Fehler?

Wähle die Zeile mit dem Fehler.

Die Idee

try und except

Eine Exception kann man fangen. Der Code, der werfen könnte, kommt in try („versuche“). Was bei einem Fehler passieren soll, schreibst du in except („außer“, also „außer wenn das passiert“):

text = input("Wie viele Tickets? ")
try:
tickets = int(text)
print(f"Gesamt: {tickets * 15} €")
except ValueError:
print("Das ist keine ganze Zahl.")
print("Vielen Dank!")
Eingabe
12a
Ausgabe
Wie viele Tickets? Das ist keine ganze Zahl.
Vielen Dank!
  • Nach except schreibst du, welche Art von Exception du fängst, hier ValueError.
  • Wenn im try alles gut geht, wird except übersprungen. Mit der Eingabe 12 ist die Ausgabe „Gesamt: 180 €“ und „Vielen Dank!“.
  • Nach except macht das Programm weiter. Es hält nicht mehr an.

text = input("Wie viele Tickets? ")
try:
tickets = int(text)
print(f"Gesamt: {tickets * 15} €")
except ValueError:
print("Das ist keine ganze Zahl.")
print("Vielen Dank!")
Schritt für Schritt, in Worten
  1. Das Programm fragt, wie viele Tickets. Du tippst „12a“.
  2. Im try: int() versucht, aus „12a“ eine Zahl zu machen.
  3. Das geht nicht. Die Funktion wirft ValueError.
  4. Die Ausführung springt direkt in except. Die Zeile mit „Gesamt“ wird übersprungen.
  5. except ist für genau diese Art von Fehler da: Es gibt die Meldung für den Kunden aus.
  6. Nach except macht das Programm weiter. Es hält nicht an.
Genauer, für Neugierige
  • In Python existiert eine Variable, die im try entsteht, auch danach: Blöcke haben keinen eigenen Gültigkeitsbereich. Aber wenn int() geworfen hat, wird tickets gar nicht erst erzeugt, und print(tickets) nach dem Block hält mit NameError an.
try:
tickets = int("12")
except ValueError:
print("Das ist keine ganze Zahl.")
print(tickets)
Ausgabe
12
  • Der Name der Exception ist optional: except ValueError:. Wenn du ihn brauchst, schreibst du except ValueError as e:.

Probier es aus

Welche Zeilen laufen?

Was gibt das Programm aus?

print("Start")
try:
print("Ich lese")
seats = int("zehn")
print(f"Gelesen: {seats}")
except ValueError:
print("Falsche Eingabe")
print("Ende")
Antwort

Die Idee

Was in der Exception steht

Die Exception ist ein Objekt, und e in except ValueError as e: ist die Variable, die darauf zeigt. Die Meldung ist der Text des Objekts selbst: str(e), oder einfach {e} in einem f-String.

try:
tickets = int(input())
print(f"Gesamt: {tickets * 15} €")
except ValueError as e:
print(f"Fehler: {e}")
Eingabe
12a
Ausgabe
Fehler: invalid literal for int() with base 10: '12a'

Die Meldung ist nützlich für dich, wenn du den Fehler suchst. Dem Kunden zeigst du meistens deinen eigenen Text in seiner Sprache, etwa „Das ist keine ganze Zahl.“

Die Idee

Frag, bis es stimmt

Der Kunde hat sich vertippt. Du musst nicht anhalten. Frag noch einmal:

while True:
try:
tickets = int(input("Wie viele Tickets? "))
break
except ValueError:
print("Gib eine ganze Zahl ein, zum Beispiel 3.")
print(f"Gesamt: {tickets * 15} €")
Eingabe
12a
zwölf
12
Ausgabe
Wie viele Tickets? Gib eine ganze Zahl ein, zum Beispiel 3.
Wie viele Tickets? Gib eine ganze Zahl ein, zum Beispiel 3.
Wie viele Tickets? Gesamt: 180 €

while True läuft immer weiter, bis break die Schleife beendet. Und break wird nur erreicht, wenn int() Erfolg hat. Bei einem Fehler springt die Ausführung in except, danach beginnt die Schleife von vorn und fragt noch einmal.

Genauer, für Neugierige
  • Python hat kein TryParse. ValueError zu fangen ist der übliche Weg: „versuch es und fang den Fehler“ ist in Python eine Gewohnheit. Es gibt text.isdigit(), aber das sagt auch für "-3" False, ersetzt int() also nicht:
print("12".isdigit())
print("12a".isdigit())
print("-3".isdigit())
Ausgabe
True
False
False

Probier es aus

Division durch null

Nicht jede Exception kommt von der Eingabe. Der durchschnittliche Bestellwert ist der Umsatz geteilt durch die Zahl der Bestellungen. Aber am ersten Tag gibt es keine Bestellungen. Mit Ganzzahldivision hält revenue // orders das Programm an:

Fehler
ZeroDivisionError: integer division or modulo by zero

Und mit der gewöhnlichen Division /, die eine Kommazahl ergibt?

Was gibt das Programm aus?

revenue = 90
orders = 0
average = revenue / orders
print(average)
Antwort
Genauer, für Neugierige
  • Die Kommazahlen von Python folgen dem Standard IEEE 754 und kennen unendlich und nan, aber die Division durch null liefert sie nicht: Python wirft lieber. Unendlich bekommst du nur, wenn du es ausdrücklich verlangst:
print(float("inf"))
print(float("inf") - float("inf"))
Ausgabe
inf
nan

Übung

Programme, die nicht anhalten

Löse die Aufgaben in deinem Editor. Gib keine Eingabeaufforderungen aus, damit deine Ausgabe mit dem Beispiel übereinstimmt.

1. Text zu Zahl. Schreibe eine Funktion to_number(text), die den Text als float zurückgibt. Lies zwei Zeilen. Gib für jede die Zahl aus oder eine Meldung, dass es keine Zahl ist. Fange die Exception dort, wo du die Funktion aufrufst.

Eingabe
123.45
123.abc
Ausgabe
Die Zahl ist: 123.45
Fehler: 123.abc ist keine Zahl.

2. Kein solches Produkt. Die Preise stehen in der Liste [120, 75, 300, 45, 89]. Lies eine Produktnummer von 1 bis 5 und gib ihren Preis aus. Wenn die Nummer außerhalb der Liste liegt, fange die Exception und sag es. Mit der Eingabe 2 ist die Ausgabe Preis: 75 €.

Eingabe
7
Ausgabe
Es gibt kein Produkt mit der Nummer 7.

Vorsicht bei der Nummer 0: In Python wirft prices[-1] nicht. Es ist der letzte Preis, 89 €. Ein negativer Index zählt vom Ende. Füge für 0 also auch ein if hinzu.

3. Durchschnittliche Bestellung. Lies den Umsatz und die Zahl der Bestellungen (ganze Zahlen) und gib den Durchschnitt mit Ganzzahldivision aus. Wenn es keine Bestellungen gibt, fange die Exception. Mit der Eingabe 900 und 4 ist die Ausgabe Durchschnitt: 225 €.

Eingabe
90
0
Ausgabe
Noch keine Bestellungen.

Weitere Aufgaben

  • Alter, bis es stimmt. Frage nach dem Alter, bis die Eingabe eine ganze Zahl von 0 bis 120 ist. Bei Text wie „zwanzig“ sag „Das ist keine Zahl.“, und bei 150 sag „Das Alter muss zwischen 0 und 120 liegen.“ Das eine ist eine Exception, das andere ein if.
  • Summe der gültigen. Lies 5 Zeilen. Addiere die, die ganze Zahlen sind, und zähle die übersprungenen: Mit der Eingabe 10, abc, 5, 7.5, 3 ist die Ausgabe Summe: 18, übersprungen: 2.
  • Division auf Wunsch. Lies zwei ganze Zahlen und gib den Quotienten aus. Bei Text statt einer Zahl und bei Division durch null hält das Programm nicht an. Es fragt noch einmal nach beiden Zahlen.

Test

Teste dich

1. Was ist eine Exception?
2. Das Programm ist mit einem Traceback angehalten. Wo fängst du an zu lesen?
3. In einem try stehen vier Zeilen. Die zweite wirft eine Exception, und except fängt sie. Was passiert mit der dritten und der vierten?
4. Was passiert bei average = revenue / orders, wenn orders 0 ist?
5. Fängt except ValueError: den Fehler von scores[10] in einer Liste mit 5 Elementen?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·