Fehler und Ausnahmen

Fehler gehören für Programmierer zum Alltag: Komplexe Computerprogramme laufen nur selten fehlerfrei, schon gar nicht in der Entwicklungsphase. Die möglichen auftretenden Fehler lassen sich allgemein in drei Arten unterteilen:

Arten von Programm-Fehlern

  • Syntax-Fehler bewirken, dass ein Programm aufgrund eines “Grammatik-Fehlers” vom Computer nicht in ausführbaren Maschinencode übersetzt werden kann. Derartige Fehler können aus unvollständigen Klammerpaaren, fehlenden Doppelpunkt-Zeichen am Ende einer if-Bedingung, ungültige Zeichen oder ähnlichem bestehen.

    Enthält ein Programm Syntax-Fehler, so werden diese beim Versuch eines Programmaufrufs angezeigt, und das Programm startet nicht.

    Die zwei geläufigsten Syntax-Checker für Python-Code sind pylint und pyflakes; zudem gibt es einen “Style”-Checker namens pep8, der prüft, ob die offiziellen Empfehlung für das Aussehen von Python-Code eingehalten werden (beispielsweise keine Zeilen mit mehr als 80 Zeichen auftreten). Diese zusätzlichen Werkzeuge können folgendermaßen installiert werden:

    pip3 install pylint
    pip3 install pyflakes
    pip3 install pep8
    

    Gibt man nach der Installation pylint skriptname.py ein, so werden einerseits darin möglicherweise enthaltene Syntax-Fehler aufgelistet, andererseits werden Empfehlungen zur Verbesserung der Code-Syntax gegeben – unter anderem wird darauf hingewiesen, wenn eine Funktionsdefinition keinen Docstring enthält.

    Gibt man pep8 skriptname.py ein, so werden ebenfalls Verbesserungsvorschläge angezeigt, wie der enthaltene Python-Code in einer besser lesbaren Form geschrieben werden kann.

  • Laufzeit-Fehler treten auf, wenn ein Programm versucht, eine ungültige Operation durchzuführen, beispielsweise eine Division durch Null oder ein Öffnen einer nicht vorhandenen Datei. Laufzeit-Fehler treten also erst auf, wenn das Programm (in der Regel ohne Fehlermeldung) bereits läuft.

    Laufzeit-Fehler können in Python allgemein mittels try-except-Konstrukten abgefangen werden.

  • Logische Fehler treten auf, wenn ein Programm zwar (anscheinend) fehlerfrei funktioniert, jedoch andere Ergebnisse liefert als erwartet. Bei solchen Fehlern liegt das Problem also an einem Denkfehler des Programmierers.

    Logische Fehler sind oft schwer zu finden; am besten hilft hierbei ein Debugger, mit dem man den Programmablauf Schritt für Schritt nachvollziehen kann (siehe Abschnitt pdb – der Python-Debugger).

Tritt in einem Python-Programm ein Fehler auf, der nicht von einer entsprechenden Routine abgefangen wird, so wird das Progarmm beendet und ein so genannter “Traceback” angezeigt. Bei dieser Art von Fehlermeldung, die man von unten nach oben lesen sollte, wird als logische Aufruf-Struktur angezeigt, welche Funktion beziehungsweise Stelle im Programm den Fehler verursacht hat; für diese Stelle wird explizit der Dateiname und die Zeilennummer angegeben.

try, except und finally – Fehlerbehandlung

Mit dem Schlüsselwort try wird eine Ausnahmebehandlung eingeleitet: Läuft der try-Block nicht fehlerfrei durch, so kann der Fehler mittels einer except-Anweisung abgefangen werden; in diesem Fall werden alle Anweisungen des jeweiligen except-Blocks ausgeführt.

Optional kann neben einer try und einer beziehungsweise mehreren except-Anweisungen auch eine else-Anweisung angegeben werden, die genau dann ausgeführt wird, wenn die try-Anweisung keinen Fehler ausgelöst hat:

try:
    # Diese Anweisung kann einen FileNotFoundError auslösen:
    file = open('/tmp/any_file.txt')

except FileNotFoundError:
    print("Datei nicht gefunden!")

except IOError:
    print("Datei nicht lesbar!")

else:
    # Datei einlesen, wenn kein Fehler augetreten ist:
    data = file.read()

finally:
    # Diese Anweisung in jedem Fall ausführen:
        file.close()

Zusätzlich zu try und except kann man optional auch einen finally-Block angeben; Code, der innerhalb von diesem Block steht, wird auf alle Fälle am Ende der Ausnahmebehandlung ausgeführt, egal ob der try-Block fehlerfrei ausgeführt wurde oder eine Exception aufgetreten ist.

Das with-Statement

Ausnahmebehandlungen sind insbesondere wichtig, wenn Dateien eingelesen oder geschrieben werden sollen. Tritt nämlich bei der Bearbeitung ein Fehler auf, so muss das file-Objekt trotzdem wieder geschlossen werden. Mit einer “klassischen” try- und finally-Schreibweise sieht dies etwa wie folgt aus:

# Datei-Objekt erzeugen:
myfile = open("filename.txt", "r")

try:
    # Datei einlesen und Inhalt auf dem Bildschirm ausgeben:
    content = myfile.read()
    print(content)
finally:
    # Dateiobjekt schließen:
    f.close()

Diese verhältnismäßig häufig vorkommende Routine kann kürzer und eleganter auch mittels eines with-Statements geschrieben werden:

with open("filename.txt", "r") as myfile:
    content = myfile.read()
    print(content)

Hierbei versucht Python ebenfalls, den with-Block ebenso wie einen try-Block auszuführen. Die Methode ist allerdings wesentlich “objekt-orientierter”: Durch die im with-Statement angegebene Anweisung wird eine Instanz eines Objekts erzeugt, in dem obigen Beispiel ein file-Objekt; innerhalb des with-Blocks kann auf dieses Objekt mittels des hinter dem Schlüsselwort as angegebenen Bezeichners zugegriffen werden.

In der Klasse des durch das with-Statement erzeugten Objekts sollten die beiden Methoden __enter__() und __exit()__ definiert sein, welche Anweisungen enthalten, die unmittelbar zu Beginn beziehungsweise am Ende des with-Blocks aufgerufen werden. Beispielsweise besitzen file-Objekte eine __exit__()-Methode, in denen die jeweilige Datei wieder geschlossen wird.

raise – Fehler selbst auslösen

Mit dem Schlüsselwort raise kann eine Ausnahme an der jeweiligen Stelle im Code selbst ausgelöst werden. Dies ist unter anderem nützlich, um bei der Interpretation einer Benutzereingabe fehlerhafte Eingaben frühzeitig abzufangen.

Wird von einem Benutzer beispielsweise anstelle einer Zahl ein Buchstabe eingegeben, so kann dies beim Aufruf der weiterverarbeitenden Funktion mit großer Wahrscheinlichkeit zu Fehlern führen. Da der Fehler jedoch bei der Eingabe entstanden ist, sollte auch an dieser Stelle die entsprechende Fehlermeldung (ein ValueError) ausgelöst werden.