Python3-Fehler und Ausnahmen

Als Python-Anfänger sehen Sie beim ersten Erlernen der Python-Programmierung oft einige Fehlermeldungen. Wir haben sie bisher nicht erwähnt; dieses Kapitel wird sie speziell vorstellen.

Python hat zwei Arten von Fehlern, die leicht zu erkennen sind: Syntaxfehler und Ausnahmen.

Python assert (Assertion) wird verwendet, um einen Ausdruck auszuwerten, und löst eine Ausnahme aus, wenn die Ausdrucksbedingung falsch ist.

Syntaxfehler

Python-Syntaxfehler, auch Parsing-Fehler genannt, treten bei Anfängern häufig auf, wie im folgenden Beispiel:

>>> while True print('Hello world')
  File "<stdin>", line 1, in ?
    while True print('Hello world')
                   ^
SyntaxError: ungültige Syntax

In diesem Beispiel wird festgestellt, dass die Funktion print() einen Fehler enthält: Es fehlt ein Doppelpunkt davor.: 。

Der Syntax-Parser zeigt auf die Zeile mit dem Fehler und markiert einen kleinen Pfeil an der Position, an der der Fehler zuerst gefunden wurde.

Ausnahmen

Selbst wenn die Syntax eines Python-Programms korrekt ist, können beim Ausführen trotzdem Fehler auftreten. Fehler, die zur Laufzeit erkannt werden, werden Ausnahmen genannt.

Die meisten Ausnahmen werden vom Programm nicht behandelt und erscheinen hier in Form von Fehlermeldungen:

Beispiel

>>> 10 * (1/0)             # 0 kann nicht als Divisor verwendet werden, löst eine Ausnahme aus
Traceback (jüngster Aufruf zuletzt):
  File "<stdin>", line 1, in ?
ZeroDivisionError: Division durch Null
>>> 4 + spam*3             # spam ist nicht definiert, löst eine Ausnahme aus
Traceback (jüngster Aufruf zuletzt):
  File "<stdin>", line 1, in ?
NameError: Name 'spam' ist nicht definiert
>>> '2' + 2               # int kann nicht zu str addiert werden, löst eine Ausnahme aus
Traceback (jüngster Aufruf zuletzt):
  Datei "<stdin>", Zeile 1, in <Modul>
TypeError: kann nur str (nicht "int") verketten str

Ausnahmen treten in verschiedenen Typen auf, und diese Typen werden alle als Teil der Information ausgegeben: Die Typen im Beispiel sind ZeroDivisionError, NameError und TypeError.

Der vordere Teil der Fehlermeldung zeigt den Kontext, in dem die Ausnahme aufgetreten ist, und gibt spezifische Informationen in Form eines Aufrufstapels aus.

Ausnahmebehandlung

try/except

Ausnahmen können mit dertry/except-Anweisung abgefangen werden.

Im folgenden Beispiel wird der Benutzer aufgefordert, eine gültige Ganzzahl einzugeben, aber dem Benutzer ist es erlaubt, das Programm zu unterbrechen (mit Control-C oder einer vom Betriebssystem bereitgestellten Methode). Die Unterbrechung durch den Benutzer löst eine KeyboardInterrupt-Ausnahme aus.

while True:
    try:
        x = int(input("Bitte geben Sie eine Zahl ein: "))
        break
    except ValueError:
        print("Sie haben keine Zahl eingegeben, bitte versuchen Sie es erneut!")

Die try-Anweisung funktioniert wie folgt:

  • Zuerst wird die try-Klausel ausgeführt (die Anweisungen zwischen dem Schlüsselwort try und dem Schlüsselwort except).

  • Wenn keine Ausnahme auftritt, wird die except-Klausel ignoriert, und die try-Klausel endet nach der Ausführung.

  • Wenn während der Ausführung der try-Klausel eine Ausnahme auftritt, wird der Rest der try-Klausel ignoriert. Wenn der Typ der Ausnahme mit dem Namen nach except übereinstimmt, wird die entsprechende except-Klausel ausgeführt.

  • Wenn eine Ausnahme mit keinem except übereinstimmt, wird diese Ausnahme an ein äußeres try weitergegeben.

Eine try-Anweisung kann mehrere except-Klauseln enthalten, um verschiedene spezifische Ausnahmen jeweils zu behandeln. Höchstens ein Zweig wird ausgeführt.

Der Handler behandelt nur Ausnahmen in der entsprechenden try-Klausel, nicht Ausnahmen in Handlern anderer try-Anweisungen.

Eine except-Klausel kann mehrere Ausnahmen gleichzeitig behandeln; diese Ausnahmen werden in Klammern gesetzt, um ein Tupel zu bilden, zum Beispiel:

except (RuntimeError, TypeError, NameError):
    pass

Die letzte except-Klausel kann den Ausnahmenamen weglassen, und sie wird als Platzhalter verwendet. Sie können diese Methode verwenden, um eine Fehlermeldung auszugeben und die Ausnahme dann erneut auszulösen.

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise

try/except...else

try/exceptDie Anweisung hat außerdem eine optionaleelse-Klausel. Wenn diese Klausel verwendet wird, muss sie nach allen except-Klauseln stehen.

Die else-Klausel wird ausgeführt, wenn in der try-Klausel keine Ausnahme auftritt.

Das folgende Beispiel prüft in einer try-Anweisung, ob die Datei geöffnet werden kann. Wenn die Datei ohne Ausnahme normal geöffnet wird, wird der else-Teil ausgeführt, um den Dateiinhalt zu lesen:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print('kann nicht öffnen', arg)
    else:
        print(arg, 'hat', len(f.readlines()), 'Zeilen')
        f.close()

Die Verwendung der else-Klausel ist besser, als alle Anweisungen in die try-Klausel zu setzen, weil sie einige unerwartete Ausnahmen vermeidet, die except nicht abfangen kann.

Die Ausnahmebehandlung behandelt nicht nur Ausnahmen, die direkt in der try-Klausel auftreten, sondern auch Ausnahmen, die in Funktionen ausgelöst werden, die in der Klausel aufgerufen werden (sogar indirekt aufgerufene Funktionen). Zum Beispiel:

>>> def this_fails():
        x = 1/0
   
>>> try:
        this_fails()
    except ZeroDivisionError as err:
        print('Laufzeitfehler wird behandelt:', err)
   
Laufzeitfehler wird behandelt: int division or Modulo durch Null

try-finally-Anweisung

Die try-finally-Anweisung führt den abschließenden Code aus, unabhängig davon, ob eine Ausnahme auftritt.

Im folgenden Beispiel wird die finally-Anweisung ausgeführt, unabhängig davon, ob eine Ausnahme auftritt:

Beispiel

try:
    example()
except AssertionError as error:
    print(error)
else:
    try:
        with open('file.log') as file:
            read_data = file.read()
    except FileNotFoundError as fnf_error:
        print(fnf_error)
finally:
    print('Dieser Satz wird unabhängig davon ausgeführt, ob eine Ausnahme auftritt.')

Ausnahmen auslösen

Python verwendet die raise-Anweisung, um eine bestimmte Ausnahme auszulösen.

Das Format der raise-Syntax lautet wie folgt:

raise [Exception [, args [, traceback]]]

Im folgenden Beispiel wird eine Ausnahme ausgelöst, wenn x größer als 5 ist:

x = 10
if x > 5:
    raise Exception('x darf nicht größer als 5 sein. Der Wert von x ist: {}'.format(x))

Das Ausführen des obigen Codes löst eine Ausnahme aus:

Traceback (most recent call last):
  File "test.py", line 3, in <module>
    raise Exception('x 不能大于 5。x 的值为: {}'.format(x))
Exception: x 不能大于 5。x 的值为: 10

Der einzige Parameter von raise gibt die auszulösende Ausnahme an. Er muss eine Instanz einer Ausnahme oder einer Ausnahmeklasse sein (d. h. eine Unterklasse von Exception).

Wenn Sie nur wissen möchten, ob eine Ausnahme ausgelöst wurde, und sie nicht behandeln möchten, kann eine einfache raise-Anweisung sie erneut auslösen.

>>> try:
        raise NameError('HiThere')  # Simuliere eine Ausnahme.
    except NameError:
        print('Eine Ausnahme flog vorbei!')
        raise
   
Eine Ausnahme flog vorbei!
Traceback (jüngster Aufruf zuletzt):
  File "<stdin>", line 2, in ?
NameError: HiThere

Benutzerdefinierte Ausnahmen

Sie können Ihre eigenen Ausnahmen haben, indem Sie eine neue Ausnahmeklasse erstellen. Die Ausnahmeklasse erbt entweder direkt oder indirekt von der Klasse Exception, zum Beispiel:

>>> class MyError(Exception):
        def __init__(self, value):
            self.value = value
        def __str__(self):
            return repr(self.value)
   
>>> try:
        raise MyError(2*2)
    except MyError as e:
        print('Meine Exception ist aufgetreten, Wert:', e.value)
   
Meine Exception ist aufgetreten, Wert: 4
>>> raise MyError('Ups!')
Traceback (jüngster Aufruf zuletzt):
  File "<stdin>", line 1, in ?
__main__.MyError: 'Ups!'

In diesem Beispiel wird die Standard-__init__() der Exception-Klasse überschrieben.

Wenn ein Modul mehrere verschiedene Exceptions auslösen kann, ist es üblich, eine Basis-Exception-Klasse für dieses Paket zu erstellen und dann auf Basis dieser Basisklasse verschiedene Unterklassen für unterschiedliche Fehlersituationen zu erstellen:

class Error(Exception):
    """Basisklasse für Exceptions in diesem Modul."""
    pass

class InputError(Error):
    """Exception ausgelöst bei Fehlern in der Eingabe.

    Attribute:
        expression -- Eingabeausdruck, in dem der Fehler aufgetreten ist
        message -- Erklärung des Fehlers
    """


    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Wird ausgelöst, wenn eine Operation einen Zustandsübergang versucht, der nicht
    erlaubt ist.

    Attribute:
        previous -- Zustand zu Beginn des Übergangs
        next -- angestrebter neuer Zustand
        message -- Erklärung, warum der spezifische Übergang nicht erlaubt ist
    """


    def __init__(self, previous, next, message):
        self.vorherige = vorherige
        self.nächste = nächste
        self.message = message

Die meisten Ausnahmenamen enden mit 'Error', genau wie die standardmäßige Benennung von Ausnahmen.


Definieren von Aufräumaktionen

Die try-Anweisung hat eine weitere optionale Klausel, die Aufräumaktionen definiert, die unter allen Umständen ausgeführt werden. Zum Beispiel:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Goodbye, world!')
...
Goodbye, world!
Traceback (jüngster Aufruf zuletzt):
  Datei "<stdin>", Zeile 2, in <Modul>
KeyboardInterrupt

Im obigen Beispiel wird die finally-Klausel unabhängig davon ausgeführt, ob eine Ausnahme in der try-Klausel auftritt.

Wenn eine Ausnahme in der try-Klausel (oder in den except- und else-Klauseln) ausgelöst wird und keine except-Klausel sie abfängt, dann wird diese Ausnahme ausgelöst, nachdem die finally-Klausel ausgeführt wurde.

Das folgende ist ein komplexeres Beispiel (das sowohl except- als auch finally-Klauseln in derselben try-Anweisung enthält):

>>> def divide(x, y):
        try:
            result = x / y
        except ZeroDivisionError:
            print("division by zero!")
        else:
            print("Ergebnis ist", result)
        finally:
            print("finally-Klausel wird ausgeführt")
   
>>> divide(2, 1)
result ist 2.0
Ausführen der finally- Klausel
>>> divide(2, 0)
Division durch Null!
Ausführen der finally- Klausel
>>> divide("2", "1")
Ausführen der finally- Klausel
Traceback (jüngster Aufruf zuletzt):
  File "<stdin>", line 1, in ?
  Datei "<stdin>", Zeile 3, in divide
TypeError: nicht unterstützter Operand typ(s) für /: 'str' und 'str'

Vordefinierte Bereinigungsaktionen

Einige Objekte definieren ein standardmäßiges Bereinigungsverhalten. Unabhängig davon, ob das System es erfolgreich genutzt hat, wird dieses standardmäßige Bereinigungsverhalten ausgeführt, sobald es nicht mehr benötigt wird.

Das folgende Beispiel zeigt den Versuch, eine Datei zu öffnen und anschließend den Inhalt auf dem Bildschirm auszugeben:

for line in open("myfile.txt"):
    print(line, end="")

Das Problem mit dem obigen Code ist, dass nach der Ausführung die Datei geöffnet bleibt und nicht geschlossen wird.

Die with-Schlüsselwort-Anweisung kann sicherstellen, dass Objekte wie Dateien nach der Verwendung ihre Bereinigungsmethode korrekt ausführen:

with open("myfile.txt") as f:
    for line in f:
        print(line, end="")

Nachdem der obige Code ausgeführt wurde, wird die Datei f immer geschlossen, selbst wenn während der Verarbeitung ein Problem auftritt.

Weitere Inhalte zum with-Schlüsselwort finden Sie unter:Python with-Schlüsselwort


Verwandte Inhalte

Python assert (Assertion)

Python with-Schlüsselwort

Andere Erweiterungen