Interaktives Python-Tutorial

Debugging in Python

Im Gegensatz zu den meisten Kursen im Studium sind Fehler beim Programmieren normal und nicht sofort schlecht, denn anders als in den meisten Kursen erhältst du beim Programmieren sofortige Rückmeldung über diese Fehler und wie du sie beheben kannst. Dieses Tutorial zeigt dir, wie du Fehlermeldungen liest, die eigentliche Ursache eines Problems findest und gezielt behebst.

Die Aufgaben beginnen einfach und werden schrittweise anspruchsvoller. Bearbeite sie der Reihe nach, denn jede Übung baut auf der vorherigen auf.

Aufgabe 1 von 6 Fehlermeldungen lesen
Tipp: Lies immer zuerst die letzte Zeile des Tracebacks.
Block 1 · Aufgaben 1–3

Den Fehlertyp erkennen

Jede Python-Fehlermeldung beginnt mit einem Typ, zum Beispiel TypeError oder ValueError. Dieser Typ sagt dir sofort, welche Art von Problem vorliegt, noch bevor du eine einzige Zeile Code anschaust. Übe hier, Fehlermeldungen schnell einzuordnen.

Ziel: Fehlertypen kennenlernen
TypeError

Eine Operation wird auf inkompatible Typen angewendet z.B. int([1, 2, 3]).

ValueError

Der Typ stimmt, aber der Inhalt passt nicht z.B. a, b = 1, 2, 3.

NameError

Ein Name (Variable, Funktion) wird verwendet, der nie definiert wurde.

IndexError

Ein Listenindex liegt ausserhalb des gültigen Bereichs z.B. liste[5] bei einer 3-Element-Liste.

KeyError

Ein Dictionary-Schlüssel existiert nicht z.B. d["email"] wenn "email" nicht im Dict ist.

AttributeError

Ein Objekt hat kein Attribut oder keine Methode dieses Namens z.B. hund.alter wenn alter nie definiert wurde.

ZeroDivisionError

Division durch null z.B. 10 / 0 oder sum / len(liste) wenn die Liste leer ist.

FileNotFoundError

Eine Datei existiert nicht am angegebenen Pfad oder das Arbeitsverzeichnis stimmt nicht.

Aufgabe 1: Welcher Fehlertyp liegt vor?

name = input("Dein Name: ")
print("Hallo, " + 42)

Aufgabe 2: Welcher Fehlertyp liegt vor?

alter = int("zwanzig")

Aufgabe 3: Welcher Fehlertyp liegt vor?

def begrüssung():
    print("Hallo, " + vorname)

begrüssung()
Block 2 · Aufgaben 4–6

Einen Traceback lesen

Ein Traceback zeigt den Aufrufstapel und zwar von der äussersten Funktion bis zur Zeile, die den Fehler ausgelöst hat. Lies ihn von unten nach oben: Die letzte eigene Codezeile vor der Fehlermeldung ist meistens der Ausgangspunkt. Klicke auf die Zeile, an der du zuerst mit der Analyse beginnen würdest.

Ziel: die relevante Zeile im Traceback finden

Aufgabe 4: Welche Zeile ist der Ausgangspunkt des Fehlers?

Traceback (most recent call last)

Aufgabe 5: Welche Zeile ist der Ausgangspunkt des Fehlers?

Traceback (most recent call last)

Aufgabe 6: Welche Zeile ist der Ausgangspunkt des Fehlers?

Traceback (most recent call last)
Block 3 · Aufgaben 7–8

Die eigentliche Ursache benennen

Den Fehlertyp kennen ist der erste Schritt. Der zweite ist zu verstehen, warum er aufgetreten ist. Analysiere den Code und wähle die Erklärung, die die Ursache und nicht nur das Symptom beschreibt.

Ziel: von Symptom zur Ursache

Aufgabe 7: Was ist die eigentliche Ursache des Fehlers?

import os

def konfiguration_laden(pfad):
    with open(pfad) as f:
        return f.read()

konfiguration_laden("einstellungen.txt")

Python wirft FileNotFoundError: [Errno 2] No such file or directory: 'einstellungen.txt'

Aufgabe 8: Was ist die eigentliche Ursache des Fehlers?

class Tier:
    def __init__(self, name):
        self.name = name

hund = Tier("Bello")
print(hund.alter)

Python wirft AttributeError: 'Tier' object has no attribute 'alter'

Block 4 · Aufgaben 9–11

Den richtigen Fix auswählen

Für jeden Fehler gibt es mehrere Wege, aber nicht alle beheben die eigentliche Ursache. Einige Fixes verstecken das Problem nur oder erzeugen neue Fehler an anderer Stelle. Wähle jeweils den Fix, der die Ursache direkt und sauber löst.

Ziel: die Ursache beheben, nicht das Symptom

Aufgabe 9: Welcher Fix ist am sinnvollsten?

werte = ["3", "4", "5"]
gesamt = 0
for w in werte:
    gesamt += w   # TypeError hier

print(gesamt)

Aufgabe 10: Welcher Fix ist am sinnvollsten?

def ersten_eintrag(liste):
    return liste[0]   # IndexError wenn Liste leer

ergebnis = ersten_eintrag([])

Aufgabe 11: Welcher Fix ist am sinnvollsten?

eingabe = input("Gib eine Zahl ein: ")
ergebnis = 100 / int(eingabe)   # möglicher ZeroDivisionError oder ValueError
print(ergebnis)
Block 5 · Aufgaben 12–13

Debugging als Methode

Gutes Debugging ist kein Raten — es ist ein systematischer Prozess. Hier geht es darum, die richtigen Fragen in der richtigen Reihenfolge zu stellen.

Ziel: eine Debugging-Gewohnheit entwickeln
1
Fehlermeldung lesen: Welcher Typ? Was sagt die letzte Zeile des Tracebacks?
2
Erwartung formulieren: Was hat der Code an dieser Stelle erwartet, was hat er bekommen?
3
Kleinste Änderung: Eine Sache ändern, die die Ursache behebt und nicht mehrere auf einmal.
4
Verhalten prüfen: Erneut ausführen und das Verhalten prüfen, nicht nur das Fehlen der Fehlermeldung.
5
Verstehen dokumentieren: Was war die Ursache? Ein kurzer Kommentar verhindert denselben Fehler morgen.

Aufgabe 12: Du hast einen Fehler gefunden. Was ist der nächste richtige Schritt?

def verbinde(a, b, c):
    return a + b + c

# Aufruf:
verbinde("Hallo", " ", 42)  # TypeError

Aufgabe 13: Welche Exception-Behandlung ist in diesem Fall am besten?

def alter_einlesen():
    eingabe = input("Dein Alter: ")
    return int(eingabe)
Optionales Kapitel · Aufgaben 14–16

Eigene Fehlerklassen schreiben

Wenn die eingebauten Exceptions zu allgemein sind, kannst du eigene definieren. Das macht deinen Code ausdrucksstärker und erleichtert es dem Aufrufer, gezielt auf bestimmte Probleme zu reagieren.

Freiwillig

Stell dir vor, du schreibst ein System, das Übungsabgaben prüft. Ein ValueError wäre zu unspezifisch, da er aus vielen anderen Teilen des Codes kommen könnte. Eine eigene Exception macht klar, woher das Problem stammt:

class UngueltigeAbgabeError(Exception):
    """Wird ausgelöst, wenn eine Abgabe formal korrekt, aber inhaltlich ungültig ist."""
    pass


class LeereAbgabeError(UngueltigeAbgabeError):
    """Spezialisierung: die Abgabe ist leer."""
    pass


def abgabe_prüfen(antwort: str) -> None:
    if not antwort.strip():
        raise LeereAbgabeError("Leere Antworten werden nicht akzeptiert.")
    if len(antwort) < 10:
        raise UngueltigeAbgabeError("Die Antwort ist zu kurz.")

Durch die Vererbung (LeereAbgabeError erbt von UngueltigeAbgabeError) kann der Aufrufer entweder gezielt auf den spezifischen Typ oder allgemein auf den Basistyp reagieren.

Aufgabe 14: Von welcher Klasse sollte eine eigene Exception erben?

Aufgabe 15: Was passiert, wenn du except UngueltigeAbgabeError schreibst, aber ein LeereAbgabeError ausgelöst wird?

try:
    abgabe_prüfen("")
except UngueltigeAbgabeError as e:
    print("Abgabefehler:", e)

Aufgabe 16: Welche dieser Exception-Definitionen ist am besten geschrieben?