Abschnitt 9 · Lektion 4
Selbst einen Fehler auslösen
Eine Funktion rechnet 150 Prozent Rabatt aus und gibt -20 Euro zurück. Keine Meldung, nichts. Python
schlägt nur Alarm, wenn es selbst nicht weitermachen kann, also bei int() mit einem Wort, bei
einer Division durch null oder bei einem Namen, den es nicht gibt. Was ein Wert bedeuten darf, weiß
man in einer eigenen Funktion aber oft viel besser als Python, und dann sollte das auch im Code
stehen.
Eine Funktion, die alles mitmacht
def preis_mit_rabatt(preis, rabatt):
return round(preis * (1 - rabatt / 100), 2)
print(preis_mit_rabatt(40, 25))
print(preis_mit_rabatt(40, 150)) preis_mit_rabatt kennst du aus der Aufgabe von Lektion 8.3, hier ohne Steuer. 25 Prozent auf 40
Euro ergeben 30 Euro. So weit gut. Bei 150 Prozent kommen -20 Euro heraus, und Python hat nichts
dagegen, weil sich das ja rechnen lässt. Nur zahlt der Laden der Kundin dann 20 Euro dafür, dass sie
den Artikel mitnimmt.
Ein Rabatt über 100 Prozent ist ein Tippfehler im Angebot. Bemerken kann man ihn am besten in der Funktion selbst.
Ablehnen mit None
def preis_mit_rabatt(preis, rabatt):
if rabatt < 0 or rabatt > 100:
return None
return round(preis * (1 - rabatt / 100), 2)
warenkorb = [(40, 25), (12, 150), (8, 0)]
summe = 0
for preis, rabatt in warenkorb:
summe = summe + preis_mit_rabatt(preis, rabatt)
print(f"Zusammen: {summe:.2f} Euro") Die erste Idee ist oft, bei Unsinn einfach None zurückzugeben, nach dem Motto „hier gibt es keinen
Preis“. Das Programm darunter rechnet einen Warenkorb mit drei Artikeln zusammen und endet mit
TypeError: unsupported operand type(s) for +: 'float' and 'NoneType'.
Der Traceback zeigt auf die Zeile mit dem Plus, also auf die Stelle, an der mit dem None gerechnet
werden sollte. Von den 150 Prozent Rabatt steht dort nichts. Das ist die Lage vom Anfang dieses
Abschnitts. Der Fehler meldet sich weit weg von seiner Ursache, und wenn du ihn suchst, musst du
erst herausfinden, woher das None kam. Es kann sogar noch schlimmer kommen. Wenn an der
aufrufenden Stelle niemand nachprüft, merkt man unter Umständen gar nichts, weil ein None in einer
Liste oder in einem Satz einfach mitreist.
Falsch ist None als Rückgabe deswegen nicht. Die Wetterstation in Lektion 8.4 gibt für eine leere
Liste dreimal None zurück, und das ist richtig so. Eine Woche ohne Messwerte ist kein Fehler. Es
gibt nur keinen Wert, und beim Aufruf wird mit is None danach gefragt. Einen Rabatt von 150
Prozent darf es dagegen gar nicht geben. Für „nichts da“ passt None, für „so nicht erlaubt“ passt
raise.
Ablehnen mit raise
def preis_mit_rabatt(preis, rabatt):
if rabatt < 0 or rabatt > 100:
raise ValueError(f"Rabatt muss zwischen 0 und 100 Prozent liegen, nicht {rabatt}")
return round(preis * (1 - rabatt / 100), 2)
print(preis_mit_rabatt(40, 25))
print(preis_mit_rabatt(12, 150)) Mit raise (auslösen) löst eine Funktion selbst einen Fehler aus. Dahinter steht die Fehlerart und
in Klammern die Meldung. Das ist ein ganz gewöhnlicher Text, gern ein f-String. Ab der Zeile mit
raise läuft die Funktion nicht weiter, und es kommt auch nichts zurück. Für die Stelle, an der sie
aufgerufen wurde, ist das dasselbe wie ein Fehler von Python. Wenn ihn niemand abfängt, endet das
Programm mit einem Traceback.
Und der sieht jetzt so aus, wie man ihn sich wünscht. Die unterste Fundstelle ist die Zeile mit dem
raise, und in der letzten Zeile steht in eigenen Worten, was los ist.
ValueError: Rabatt muss zwischen 0 und 100 Prozent liegen, nicht 150. Da muss niemand mehr suchen.
ValueError heißt, der Wert hat den richtigen Typ, aber den falschen Inhalt. 150 ist eine Zahl, nur
eben keine erlaubte. Für einen Wert vom falschen Typ, etwa einen Text, wo eine Zahl hingehört, gibt
es TypeError. Mit den beiden kommst du bei fast allem aus, was eine eigene Funktion ablehnen muss.
Wer aufruft, entscheidet
def preis_mit_rabatt(preis, rabatt):
if rabatt < 0 or rabatt > 100:
raise ValueError(f"Rabatt muss zwischen 0 und 100 Prozent liegen, nicht {rabatt}")
return round(preis * (1 - rabatt / 100), 2)
warenkorb = [(40, 25), (12, 150), (8, 0)]
summe = 0
for preis, rabatt in warenkorb:
try:
summe = summe + preis_mit_rabatt(preis, rabatt)
except ValueError as fehler:
print("Nicht berechnet:", fehler)
print(f"Zusammen: {summe:.2f} Euro") Was nach der Ablehnung passiert, ist nicht mehr Sache der Funktion. Im Warenkorb wird der
ValueError mit try und except abgefangen. Dann steht Nicht berechnet: mit der Meldung da,
und mit den übrigen Artikeln wird weitergerechnet bis Zusammen: 38.00 Euro.
Ein anderes Programm könnte mit demselben Fehler ganz anders umgehen. Es könnte den Artikel zum vollen Preis berechnen, das Angebot an jemanden zurückschicken oder ganz aufhören, weil ohne den Preis nichts mehr stimmt. Davon muss die Funktion nichts wissen. Sie erkennt das Problem, und um den Rest kümmert sich die Stelle, die sie aufruft.
Diese Trennung ist der eigentliche Grund für raise. Wenn eine Funktion selbst eine Meldung ausgibt
und dann irgendetwas zurückgibt, ist die Entscheidung schon gefallen, bevor das aufrufende Programm
überhaupt gefragt wurde. Und meistens ist es die falsche.
Was die Aufgabe will
Eine Funktion ueberweise rechnet aus, was nach einer Überweisung auf dem Konto bleibt. Dabei macht
sie alles mit, auch eine Überweisung von minus 20 Euro und eine, für die das Geld nicht reicht.
Beides soll sie mit einem ValueError ablehnen, jeweils mit einer eigenen Meldung.
Zum Mitnehmen
Wenn eine Funktion mit einem Wert nichts Sinnvolles anfangen kann, lehnt sie ihn mit raise ValueError ab, statt Unsinn zurückzugeben. In die Meldung gehört, was an dem Wert nicht stimmt. Was danach passiert, wird dort entschieden, wo die Funktion aufgerufen wird, mit try und except.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 4 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.