31. Ausnahmen

Möchtest du verhindern, dass dem Benutzer eine rote Python-Fehlermeldung angezeigt wird, wenn ein Problem mit deinem Programm auftritt? Möchtest du verhindern, dass dein Programm hängt? Wenn ja, dann brauchst du Ausnahmen (englisch exceptions).

Ausnahmen werden verwendet, um abnormale Bedingungen zu behandeln, die während der Ausführung von Programmcode auftreten können. Ausnahmen werden häufig bei Datei- und Netzwerkoperationen verwendet. Auf diese Weise kann das Programm ordnungsgemäß mit Speicherplatzmangel, Netzwerkfehlern oder Berechtigungsfehlern umgehen.

31.1. Wortschatz

Beim Arbeiten mit Ausnahmen werden mehrere Begriffe und Phrasen verwendet. Hier sind die häufigsten:

  • Ausnahme: Dieser Begriff kann eines von zwei Dingen bedeuten. Erstens die Bedingung, die zu einem abnormalen Programmfluss führt. Oder er könnte verwendet werden, um auf ein Objekt zu verweisen, das die Daten der Ausnahmebedingung darstellt. Jede Ausnahme hat ein Objekt, das Informationen darüber enthält.

  • Ausnahmebehandlung: Der Prozess der Behandlung einer Ausnahme vom normalen Programmablauf.

  • Catch-Block oder Exception-Block: Code, der einen abnormalen Zustand behandelt, soll die Ausnahme „abfangen“.

  • Auslösen oder Werfen: Wenn eine abnormale Bedingung für den Programmablauf erkannt wurde, wird ein Exemplar eines Ausnahmeobjekts erstellt. Es wird dann „geworfen“ oder „ausgelöst“ zum Code, der es abfängt.

  • Unbehandelte Ausnahme oder nicht erfasste Ausnahme: Eine Ausnahme, die ausgelöst, aber nie abgefangen wird. Dies führt normalerweise zu einem Fehler und zum Beenden oder Abstürzen des Programms.

  • Try Block: Ein Codeblock, in dem möglicherweise eine Ausnahme ausgelöst wird.

Die meisten Programmiersprachen verwenden die Begriffe „throw“ und „catch“. Python tut dies leider nicht. Python verwendet „raise“ und „exception“. Wir stellen hier das Wurf-/Fangvokabular vor, da es die in der Branche am häufigsten verwendeten Begriffe sind.

31.2. Ausnahmebehandlung

Der Code für die Behandlung von Ausnahmen ist einfach. Siehe folgendes Beispiel:

Division durch Null abhandeln
1
2
3
4
5
# Divide by zero
try:
    x = 5 / 0
except:
    print("Error dividing by zero")

In Zeile zwei steht die Anweisung try. Jede eingerückte Zeile darunter ist Teil des „try-Blocks“. Unter dem try -Block muss eingerückter Code stehen. Erst der Teil, der mit einer except-Anweisung beginnt, ist nicht mehr eingerückt. Die try-Anweisung definiert einen Codeabschnitt, den der Code auszuführen versucht.

Wenn während der Verarbeitung des Codes eine Ausnahme auftritt, springt die Ausführung sofort zum „catch-Block“. Dieser Codeblock wird unter der Anweisung except in Zeile 4 eingerückt. Dieser Code ist für die Behandlung des Fehlers verantwortlich.

Ein Programm kann Ausnahmen verwenden, um Fehler abzufangen, die bei der Konvertierung von Text in eine Zahl auftreten. Zum Beispiel:

Umgang mit Fehlern bei der Konvertierung von Text in Zahlen
1
2
3
4
5
# Invalid number conversion
try:
    x = int("fred")
except:
    print("Error converting fred to a number")

In Zeile 3 wird eine Ausnahme ausgelöst, da „fred“ nicht in eine Ganzzahl konvertiert werden kann. Der Code in Zeile 5 gibt eine Fehlermeldung aus.

Unten findest du eine erweiterte Version dieses Beispiels. Es überprüft die Eingaben eines Benutzers auf Fehler, um sicherzustellen, dass eine Ganzzahl eingegeben wurde. Wenn der Benutzer keine Ganzzahl eingibt, fragt das Programm weiterhin nach einer Ganzzahl. Der Code verwendet die Ausnahmebehandlung, um einen möglichen Konvertierungsfehler zu erfassen, der in Zeile 5 auftreten kann. Wenn der Benutzer etwas anderes als eine Ganzzahl eingibt, wird eine Ausnahme ausgelöst, wenn die Konvertierung in eine Zahl in Zeile 5 erfolgt. Der Code in Zeile 6, der number_entered auf True setzt, wird nicht ausgeführt, wenn in Zeile 5 eine Ausnahme vorliegt.

Besserer Umgang mit Fehlern bei der Umwandlung von Zahlen
1
2
3
4
5
6
7
8
number_entered = False
while not number_entered:
    number_string = input("Enter an integer: ")
    try:
        n = int(number_string)
        number_entered = True
    except:
        print("Error, invalid integer")

Dateioperationen sind besonders fehleranfällig: Eine Festplatte könnte voll sein, ein Benutzer könnte eine Datei löschen, während sie geschrieben wird, sie könnte verschoben werden oder ein USB-Laufwerk könnte während des Betriebs herausgezogen werden. Diese Arten von Fehlern können ebenfalls mithilfe der Ausnahmebehandlung leicht erfasst werden.

Überprüfung auf Fehler beim Öffnen einer Datei
1
2
3
4
5
# Error opening file
try:
    my_file = open("myfile.txt")
except:
    print("Error opening file")

Mehrere Fehlertypen können erkannt und unterschiedlich verarbeitet werden. Es kann sinnvoll sein, dem Benutzer eine genauere Fehlermeldung zukommen zu lassen, als lediglich „ein Fehler ist aufgetreten“.

In the code below, different types of errors can occur from lines 3-15. By placing IOError after except on line 19, only errors regarding Input and Output (IO) will be handled by that code. Likewise line 21 only handles errors around converting values, and line 23 covers division by zero errors. The last exception handling occurs on line 25. Since line 25 does not include a particular type of error, it will handle any error not covered by the except blocks above. The „catch-all“ except must always be last.

Umgang mit verschiedenen Arten von Fehlern
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Multiple errors
try:
    # Open the file
    filename = "myfile.txt"
    my_file = open(filename)

    # Read from the file and strip any trailing line feeds
    my_line = my_file.readline()
    my_line = my_line.strip()

    # Convert to a number
    my_int = int(my_line)

    # Do a calculation
    my_calculated_value = 101 / my_int

except FileNotFoundError:
    print(f"Could not find the file '{filename}'.")
except IOError:
    print(f"Input/Output error when accessing the file '{filename}'.")
except ValueError:
    print("Could not convert data to an integer.")
except ZeroDivisionError:
    print("Division by zero error.")
except:
    print("Unexpected error.")

Eine Liste der in Python integrierten Ausnahmen finden Sie unter folgender Adresse:

http://docs.python.org/library/exceptions.html

31.3. Beispiel: Highscore speichern

Das folgende Programm zeigt, wie du zwischen den Spielen einen Highscore speichern kannst. Der Spielstand wird in einer Datei mit dem Namen high_score.txt gespeichert.

high_score.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
"""
Show how to use exceptions to save a high score for a game.

Sample Python/Pygame Programs
Simpson College Computer Science
http://simpson.edu/computer-science/
"""


def get_high_score():
    # Default high score
    high_score = 0

    # Try to read the high score from a file
    try:
        high_score_file = open("high_score.txt", "r")
        high_score = int(high_score_file.read())
        high_score_file.close()
        print("The high score is", high_score)
    except IOError:
        # Error reading file, no high score
        print("There is no high score yet.")
    except ValueError:
        # There's a file there, but we don't understand the number.
        print("I'm confused. Starting with no high score.")

    return high_score


def save_high_score(new_high_score):
    try:
        # Write the file to disk
        high_score_file = open("high_score.txt", "w")
        high_score_file.write(str(new_high_score))
        high_score_file.close()
    except IOError:
        # Hm, can't write it.
        print("Unable to save the high score.")


def main():
    """ Main program is here. """
    # Get the high score
    high_score = get_high_score()

    # Get the score from the current game
    current_score = 0
    try:
        # Ask the user for his/her score
        current_score = int(input("What is your score? "))
    except ValueError:
        # Error, can't turn what they typed into a number
        print("I don't understand what you typed.")

    # See if we have a new high score
    if current_score > high_score:
        # We do! Save to disk
        print("Yea! New high score!")
        save_high_score(current_score)
    else:
        print("Better luck next time.")

# Call the main function, start up the game
if __name__ == "__main__":
    main()

31.4. Ausnahmeobjekte

Weitere Informationen zu einem Fehler können aus dem Ausnahmenobjekt abgerufen werden. Dieses Objekt bekommt man, wenn ein Fehler mit dem Schlüsselwort as abgefangen wird. Zum Beispiel:

Ausnahme erstellen
1
2
3
4
try:
    x = 5 / 0
except ZeroDivisionError as e:
    print(e)

Die Variable e zeigt auf die weitere Information zu der Ausnahme, die ausgegeben werden kann. Mit Ausnahmeobjekten kann noch mehr getan werden, was jedoch den Rahmen dieses Kapitels sprengt. Weitere Informationen zum Ausnahmeobjekt findest du in der Python-Dokumentation online.

31.5. Ausnahmen generieren

Ausnahmen können mit dem Befehl raise erzeugt werden. Zum Beispiel:

Ausnahme erstellen
1
2
3
4
5
6
7
# Generating exceptions
def get_input():
    user_input = input("Enter something: ")
    if len(user_input) == 0:
        raise IOError("User entered nothing")

get_input()

Nimm den obigen Code und füge eine Ausnahmebehandlung für den ausgelösten IOError hinzu.

Es ist auch möglich, benutzerdefinierte Ausnahmen zu erstellen, dies würde jedoch den Rahmen dieses Buches sprengen. Neugierige Leser erfahren mehr unter:

http://docs.python.org/tutorial/errors.html#raising-exceptions

31.6. Korrekte Verwendung von Ausnahmen

Ausnahmen sollten nicht verwendet werden, wenn if-Anweisungen die Bedingung genauso gut verarbeiten können. Normaler Code sollte beim Ausführen des Szenarios „Happy Path“ keine Ausnahmen auslösen. Gut konstruierter Try/Catch-Code ist einfach nachzuverfolgen, aber Code mit vielen Ausnahmen und Code-Sprüngen zu verschiedenen Handlern kann ein Albtraum für das Debuggen sein. (Mir wurde einmal die Aufgabe zugewiesen, den Code zum Lesen eines XML-Dokuments zu debuggen. Es wurden Dutzende von Ausnahmen für jede Zeile der gelesenen Datei generiert. Es war unglaublich langsam und fehleranfällig. Dieser Code hätte niemals eine einzige Ausnahme beim normalen Lesen einer Datei generieren dürfen.)