Abschnitt 9 · Lektion 1
Tracebacks über mehrere Ebenen
Sobald Funktionen andere Funktionen aufrufen, wird die rote Meldung länger. Beim ersten Mal sieht sie wirklich aus wie eine Wand. In Lektion 1.4 hatte ein Traceback eine Fundstelle, in Lektion 8.1 zwei. Die Wand ist genauso aufgebaut wie damals, und wenn du diese Ordnung kennst, liest du auch fünf Fundstellen in ein paar Sekunden.
Drei Fundstellen
def durchschnitt(werte):
return sum(werte) / len(werte)
def tageszeile(tag, werte):
schnitt = durchschnitt(werte)
return f"{tag}: im Schnitt {schnitt:.1f} Grad"
print(tageszeile("Montag", [12.5, 14.0, 13.5]))
print(tageszeile("Dienstag", []))
print(tageszeile("Mittwoch", [9.0, 11.6])) Eine kleine Wetterstation rechnet für jeden Tag den Durchschnitt der Messwerte aus. Der Montag klappt. Beim Dienstag bricht das Programm ab, und der Mittwoch kommt gar nicht mehr dran.
Gelesen wird wie immer zuerst die letzte Zeile. ZeroDivisionError: division by zero heißt Division
durch null. Darüber stehen drei Zeilen mit File. Von oben nach unten gelesen sind sie der Weg, den
das Programm bis zum Knall genommen hat.
- Zeile 11
in <module>ist das Programm selbst. Hier wirdtageszeilefür den Dienstag aufgerufen, und die Wellenlinie mit den Dächern darunter zeigt genau auf diesen Aufruf. - Zeile 6
in tageszeile. Hier rufttageszeileihrerseitsdurchschnittauf. - Zeile 2
in durchschnitt. Hier wird geteilt, und hier hat es geknallt.
Daher kommt auch die Überschrift most recent call last. Der jüngste Aufruf steht zuletzt. Ganz
oben steht, wo alles angefangen hat, und ganz unten, wo es aufgehört hat. Im Terminal ist jede der
drei Zeilen ein Button, und im Editor ist von Anfang an die unterste markiert.
Wo es knallt, und wo der Fehler liegt
Die unterste Fundstelle ist der Ort, an dem Python das Problem bemerkt hat. Ob man dort auch etwas
ändern sollte, ist eine andere Frage. In durchschnitt steht Summe durch Anzahl, und genau das wird
gerechnet. Am Dienstag gab es aber keine Messwerte. Die Liste ist leer, und durch null Werte kann
niemand teilen. Interessanter als „Was ist an Zeile 2 falsch?“ ist deshalb die Frage, wer eine leere
Liste dorthin schickt und wo entschieden werden sollte, was dann passiert.
def durchschnitt(werte):
if not werte:
return 0
return sum(werte) / len(werte)
def tageszeile(tag, werte):
schnitt = durchschnitt(werte)
return f"{tag}: im Schnitt {schnitt:.1f} Grad"
print(tageszeile("Montag", [12.5, 14.0, 13.5]))
print(tageszeile("Dienstag", []))
print(tageszeile("Mittwoch", [9.0, 11.6])) Am nächsten liegt eine Reparatur direkt am Knall. Bei einer leeren Liste gibt durchschnitt einfach
0 zurück, das Programm läuft durch, und da steht Dienstag: im Schnitt 0.0 Grad. Das ist falsch.
Und zwar leise, denn 0 Grad ist eine echte Temperatur, für die die Wetterstation sogar eine eigene
Prüfung hat.
Die Fehlermeldung ist weg, der Fehler nicht. So etwas nennt man, ein Symptom zu beheben. Was man gesehen hat, ist verschwunden. Die Ursache ist noch da und bekommt jetzt eine erfundene Antwort.
def durchschnitt(werte):
return sum(werte) / len(werte)
def tageszeile(tag, werte):
if not werte:
return f"{tag}: keine Messwerte"
schnitt = durchschnitt(werte)
return f"{tag}: im Schnitt {schnitt:.1f} Grad"
print(tageszeile("Montag", [12.5, 14.0, 13.5]))
print(tageszeile("Dienstag", []))
print(tageszeile("Mittwoch", [9.0, 11.6])) Besser fällt die Entscheidung eine Ebene höher. In tageszeile entsteht der Satz für einen Tag, und
dort weiß man auch, was „nichts gemessen“ für den Leser heißen soll, nämlich keine Messwerte.
durchschnitt bleibt, wie es war. Es bekommt nur keine leere Liste mehr.
Wenn die Ursache gar nicht im Traceback steht
def einlesen():
werte = []
for zeile in ["12.5", "14.0", "13.5"]:
werte.append(zeile)
return werte
def durchschnitt(werte):
return sum(werte) / len(werte)
messwerte = einlesen()
print(f"{len(messwerte)} Messwerte gelesen")
print(f"Im Schnitt {durchschnitt(messwerte):.1f} Grad") Diesmal endet es mit TypeError: unsupported operand type(s) for +: 'int' and 'str'. Mit Plus soll
hier eine ganze Zahl und ein Text zusammengerechnet werden. Der Traceback nennt zwei Fundstellen,
das Programm in Zeile 14 und durchschnitt in Zeile 9. Wellenlinie und Dächer stehen unter
sum(werte).
Die ganze Zahl ist der Anfang der Summe. sum fängt bei 0 an und zählt den ersten Wert dazu, und
der ist ein Text.
Die Ursache liegt in einlesen. Dort fehlt das float() um jede Zeile. Nur steht einlesen
nirgends im Traceback, weil ein Traceback nur die Aufrufe zeigt, die im Moment des Knalls noch
liefen. einlesen war da längst fertig. Die Liste aus Texten wurde zurückgegeben und ist im
Programm so lange weitergewandert, bis jemand damit rechnen wollte.
Wenn du nach der Ursache suchst, reicht die Frage nach dem Wo also nicht. Genauso wichtig ist, woher
der Wert kommt, mit dem hier gerechnet wird. Im Reiter Debug sieht man das sofort. Im letzten
Schritt der Aufzeichnung, der Zeile in durchschnitt, steht werte mit ['12.5', '14.0', '13.5'].
An den Anführungszeichen erkennst du, dass das Texte sind. Wenn du zurückblätterst, siehst du die
Liste in einlesen entstehen, Runde für Runde um einen Text länger. Wie man so eine Spur Schritt
für Schritt verfolgt, kommt im letzten Teil dieses Abschnitts.
Was die Aufgabe will
Das Haushaltsbuch soll für jede Kategorie sagen, was eine Ausgabe dort im Schnitt gekostet hat. Bei
einer Kategorie, in der diese Woche nichts ausgegeben wurde, bricht es ab, über drei Ebenen hinweg.
Gesucht ist die Stelle, an der das entschieden werden sollte. Dort soll für so eine Kategorie
ehrlich keine Ausgaben herauskommen.
Zum Mitnehmen
Ein Traceback liest man von oben nach unten als Weg, vom ersten Aufruf bis zu der Stelle, an der es geknallt hat. Die Ursache sitzt oft weiter oben. Manchmal steht sie gar nicht drin, weil die Funktion, die den falschen Wert gebaut hat, da schon fertig war.
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.