try, except und finally
Kurz gesagt: Mit mehreren except-Blöcken fängst du verschiedene Arten von Exceptions, vom Speziellen zum Allgemeinen, und finally läuft immer, mit oder ohne Fehler.
Problem
Die Rechnung für null Personen
Das Programm teilt die Restaurantrechnung gleichmäßig auf. Falsche Eingaben kennst du schon, und du fängst sie:
try: bill = int(input()) people = int(input()) print(f"Pro Person: {bill // people} €")except ValueError: print("Gib ganze Zahlen ein.")Die Rechnung beträgt 90 €, und jemand gibt 0 als Zahl der Personen ein. Das Programm hält wieder an:
Traceback (most recent call last): File "/projects/bill/bill.py", line 4, in <module> print(f"Pro Person: {bill // people} €") ~~~~~^^~~~~~~~ZeroDivisionError: integer division or modulo by zeroexcept ist da, hilft aber nicht. Es fängt nur ValueError, und das hier ist eine andere Art von Exception.
Die Idee
Mehrere except-Blöcke
Auf ein try können mehrere except-Blöcke folgen, einer für jede Art von Fehler:
try: bill = int(input()) people = int(input()) print(f"Pro Person: {bill // people} €")except ValueError: print("Gib ganze Zahlen ein.")except ZeroDivisionError: print("Es können nicht 0 Personen sein.")| Eingabe | Ausgabe |
|---|---|
| 90 und 3 | Pro Person: 30 € |
| 90 und 0 | Es können nicht 0 Personen sein. |
| 90 und drei | Gib ganze Zahlen ein. |
Die Blöcke werden von oben nach unten geprüft. Der erste, dessen Art passt, läuft, und nur dieser. Die übrigen werden übersprungen.
try: bill = int(input()) people = int(input()) print(f"Pro Person: {bill // people} €")except ValueError: print("Gib ganze Zahlen ein.")except ZeroDivisionError: print("Es können nicht 0 Personen sein.")Schritt für Schritt, in Worten
- Erster Lauf: Die Rechnung beträgt 90 €, und es sind 0 Personen.
- 90 / 0 ist eine Division durch null: ZeroDivisionError wird geworfen.
- Das erste except ist für ValueError. Es passt nicht und wird übersprungen.
- Das zweite passt. Nur dieses läuft.
- Zweiter Lauf: Statt der Zahl der Personen wird „drei“ eingegeben.
- int() kann keine Zahl machen: ValueError wird geworfen.
- Schon das erste except passt. Das zweite wird nie erreicht.
- Die Blöcke werden von oben nach unten geprüft. Der erste, der passt, läuft.
Genauer, für Neugierige
- Wenn zwei Arten gleich behandelt werden, kannst du sie in einem Block mit einem Tupel fangen:
except (ValueError, ZeroDivisionError):.
Die Idee
Die Familie der Exceptions
Exceptions sind Klassen, die durch Vererbung verbunden sind. Hier sind die häufigsten:
BaseException├── KeyboardInterrupt Strg+C, wird nicht gefangen├── SystemExit sys.exit(), wird nicht gefangen└── Exception ├── ArithmeticError │ ├── ZeroDivisionError │ └── OverflowError ├── LookupError │ ├── IndexError │ └── KeyError ├── ValueError ├── TypeError └── OSError Dateien, Netzwerk └── FileNotFoundErrorexcept fängt die Art, die du angibst, und alle ihre Unterklassen. Das ist die Polymorphie aus dem Modul über Vererbung: Ein Objekt einer Unterklasse ist auch von der Art der Basisklasse. issubclass zeigt es:
print(issubclass(ZeroDivisionError, ArithmeticError))print(issubclass(IndexError, LookupError))print(issubclass(ValueError, Exception))print(issubclass(KeyboardInterrupt, Exception))TrueTrueTrueFalseDeshalb fängt except Exception as e: fast alles:
def split(bill_text, people_text): try: bill = int(bill_text) people = int(people_text) print(f"Pro Person: {bill // people} €") except Exception as e: print(f"{type(e).__name__}: {e}")
split("90", "3")split("90", "0")split("90", "drei")Pro Person: 30 €ZeroDivisionError: integer division or modulo by zeroValueError: invalid literal for int() with base 10: 'drei'type(e).__name__ gibt den Namen der echten Klasse des Objekts. Das except ist für Exception, aber das Objekt ist von einer Unterklasse.
Genauer, für Neugierige
KeyboardInterrupt(Strg+C) undSystemExit(sys.exit()) sind keine Unterklassen vonException, sondern vonBaseException. So verhindertexcept Exception:nicht, dass das Programm angehalten werden kann.except:ohne Art undexcept BaseException:fangen auch sie. Schreib das nicht.
- Fange so speziell, wie du kannst. except Exception: versteckt auch Fehler, die du nicht erwartet hast, etwa einen Bug in deinem eigenen Code.
Probier es aus
Welches catch?
Was gibt das Programm aus?
seats = [4, 2, 6]try: print(f"Tisch 4: {seats[3]} Plätze")except ZeroDivisionError: print("Division durch null")except LookupError: print("Fehler beim Suchen")except Exception: print("Allgemeiner Fehler")seats[3] liegt außerhalb der Liste mit 3 Elementen: IndexError. Er ist kein ZeroDivisionError, deshalb wird der erste Block übersprungen. Er ist eine Unterklasse von LookupError, deshalb passt der zweite und läuft. Der dritte wird nie erreicht, obwohl er auch passen würde.Probier es aus
Finde den Fehler
text = "0" gibt das Programm „Etwas ist schiefgegangen.“ aus und nicht „Es können nicht 0 Personen sein.“. In welcher Zeile steckt der Fehler?Wähle die Zeile mit dem Fehler.
Exception ist die Basisklasse von ZeroDivisionError. Das erste except fängt alles, und das zweite kann nie erreicht werden. Python sagt nichts: Das Programm läuft und gibt einfach die allgemeine Meldung aus. Die Regel lautet vom Speziellen zum Allgemeinen: Vertausche die beiden Blöcke.Die Idee
Niemand zwingt dich
Java hat Checked Exceptions: Der Compiler akzeptiert das Programm erst, wenn du sie fängst oder mit throws deklarierst. Python hat keine solchen. Keine Exception muss gefangen werden:
import time
def print_receipt(): print("Drucke den Kassenbon...") time.sleep(0.3) print("Fertig.")
print_receipt()Drucke den Kassenbon...Fertig.import time holt das Modul für die Zeit, und time.sleep(0.3) hält das Programm 0,3 Sekunden an. Wenn du während der Pause Strg+C drückst, wirft Python KeyboardInterrupt, aber niemand verlangt, dass du sie fängst. Dasselbe gilt für int() und ValueError.
Die Verantwortung liegt also bei dir. Welche Exceptions eine Funktion wirft, steht in ihrer Dokumentation, meistens mit dem Wort „Raises“. Lies sie, bevor du entscheidest, ob du fängst.
Die Idee
finally: immer
Der Drucker an der Kasse muss nach jedem Kassenbon freigegeben werden, sonst wird der nächste nicht gedruckt. Egal, was passiert, Erfolg oder Fehler:
def print_receipt(qty_text): print("Der Drucker ist belegt.") try: qty = int(qty_text) print(f"Kaffee: {qty} x 2 € = {qty * 2} €") except ValueError: print(f"Falsche Menge: {qty_text}") finally: print("Der Drucker ist frei.")
print_receipt("3")print_receipt("drei")Der Drucker ist belegt.Kaffee: 3 x 2 € = 6 €Der Drucker ist frei.Der Drucker ist belegt.Falsche Menge: dreiDer Drucker ist frei.finally läuft immer: wenn alles gut geht, wenn die Exception gefangen wird, und sogar wenn keiner dieser except-Blöcke sie fängt. Im letzten Fall läuft finally, und die Exception geht weiter nach oben zum nächsten try, das sie fangen kann.
Warum nicht einfach eine Zeile nach dem try/except? Weil diese Zeile übersprungen wird, wenn die Exception von einer Art ist, die except nicht fängt, oder wenn im try ein return steht. finally wird nicht übersprungen.
Python hat außerdem else: Es läuft nur, wenn im try keine Exception geworfen wurde. So steht im try nur die Zeile, die werfen kann, und das Drucken kommt danach:
def print_receipt(qty_text): print("Der Drucker ist belegt.") try: qty = int(qty_text) except ValueError: print(f"Falsche Menge: {qty_text}") else: print(f"Kaffee: {qty} x 2 € = {qty * 2} €") finally: print("Der Drucker ist frei.")
print_receipt("3")print_receipt("drei")Der Drucker ist belegt.Kaffee: 3 x 2 € = 6 €Der Drucker ist frei.Der Drucker ist belegt.Falsche Menge: dreiDer Drucker ist frei.Genauer, für Neugierige
- Es geht auch
trymitfinallyund ohneexcept: Die Exception wird nicht gefangen, aber das Aufräumen läuft.
- Für Ressourcen, die geschlossen werden müssen, etwa Dateien und Datenbankverbindungen, gibt es eine kürzere Form:
with. Wir sehen es, wenn wir zu den Dateien kommen. Diese Form schließt die Ressource von selbst, so wiefinallyes tun würde.
finallyläuft sogar beisys.exit(): In Python ist auch das eine Exception (SystemExit), und sie geht durchfinallynach oben.
Probier es aus
finally und return
Was gibt das Programm aus?
def reserve(guests): try: if guests > 6: return f"Kein Tisch für {guests}" return f"Tisch für {guests}" finally: print("Versuch protokolliert.")
print(reserve(8))Ein return im try überspringt finally nicht. Die Funktion führt zuerst finally aus und gibt dann den Text zurück. Deshalb wird „Versuch protokolliert.“ vor dem Ergebnis ausgegeben, das print erst bekommt, wenn die Funktion fertig ist.
Übung
Programme mit mehreren except-Blöcken
Löse die Aufgaben in deinem Editor. Gib keine Eingabeaufforderungen aus.
1. Preis pro Person. Die Preise stehen in der Liste [120, 75, 300]. Lies eine Produktnummer (ab 1) und die Zahl der Personen und gib den Preis pro Person mit Ganzzahldivision aus. Fange drei Dinge: ein Produkt, das es nicht gibt, null Personen und alles andere (dann gib die Meldung der Exception aus).
| Eingabe | Ausgabe |
|---|---|
| 2 und 3 | Pro Person: 25 € |
| 5 und 2 | Dieses Produkt gibt es nicht. |
| 1 und 0 | Die Zahl der Personen darf nicht 0 sein. |
| 1 und zwei | Fehler: invalid literal for int() with base 10: 'zwei' |
2. Taschenrechner. Lies eine ganze Zahl, einen Operator (+, -, * oder /) und eine zweite ganze Zahl, jede in einer eigenen Zeile. Gib die Rechnung aus (die Division ist //). Bei Text statt einer Zahl sag „Gib ganze Zahlen ein.“, bei Division durch null „Division durch null.“ und bei jedem anderen Operator „Unbekannter Operator: “ und dahinter den Operator. Für den Operator ist ein match praktisch.
84/484 / 4 = 2184/0Division durch null.Weitere Aufgaben
- Das Kartenterminal. Die Funktion
pay(amount_text)gibt „Das Terminal ist verbunden.“ aus, dann „Bezahlt: X €“ oder eine Meldung über die falsche Eingabe, und imfinally„Das Terminal ist aus.“ Rufe sie mit „25“ und mit „zwanzig“ auf. - Durchschnittsnote ohne Absturz. Lies 5 Zeilen mit Noten. Überspringe die, die keine ganzen Zahlen sind (eine Exception) oder nicht zwischen 2 und 6 liegen (ein
if). Gib den Durchschnitt mit Ganzzahldivision aus. Wenn keine Note gültig ist, wirft die Division durch null: Fange sie und gib „Keine gültigen Noten.“ aus. - Vorhersagen und prüfen. Schreibe ein
trymitexceptundfinally, in dem jeder Block eine Zeile ausgibt. Sage die Ausgabe bei Erfolg, bei einer gefangenen und bei einer nicht gefangenen Exception voraus, und führe es dann aus.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Instanzmethoden · Vererbung · Polymorphie · Exceptions
- Geht weiter in: Werfen und eigene Exceptions · Dateien · Daten in Dateien · Hashcodes und Hashfunktionen
- Aufgaben zum Thema: Fehler und Exceptions