Behandeln von Ausnahmen

Abgeschlossen

Wenn Sie zum ersten Mal Ausnahmen finden, die große Ablaufverfolgungen als Ausgabe anzeigen, werden Sie möglicherweise dazu verleitet, jeden Fehler abzufangen, um dies zu verhindern.

Was könnten Sie bei einer Mission zum Mars tun, wenn das Navigationssystem die Meldung „an error occurred“ (es ist ein Fehler aufgetreten) anzeigt? Stellen Sie sich vor, dass es keine weiteren Informationen oder Kontext gibt, sondern nur ein blinkendes rotes Licht mit dem Fehlertext. Als Entwickler ist es nützlich, sich auf die andere Seite des Programms zu versetzen: Wie kann ein Benutzer vorgehen, wenn ein Fehler auftritt?

Auch wenn in diesem Modul behandelt wird, wie Ausnahmen durch Abfangen behandelt werden, ist es nicht notwendig, Ausnahmen immer abzufangen. Manchmal ist es hilfreich, das Auslösen von Ausnahmen zuzulassen, damit andere Aufrufer die Fehler behandeln können.

Try- und Except-Blöcke

Verwenden wir das Navigatorbeispiel, um Code zu erstellen, der Konfigurationsdateien für die Mars-Mission öffnet. Konfigurationsdateien können alle Arten von Problemen aufweisen, daher ist es wichtig, Probleme genau zu melden, wenn sie auftreten. Wir wissen, dass FileNotFoundError ausgelöst wird, wenn eine Datei oder ein Verzeichnis nicht vorhanden ist. Wenn wir diese Ausnahme behandeln möchten, können wir dazu einen try- und except-Block verwenden:

try:
     open('config.txt')
except FileNotFoundError:
     print("Couldn't find the config.txt file!")

Couldn't find the config.txt file!

Nach dem try-Schlüsselwort fügen Sie Code hinzu, der eine Ausnahme auslösen kann. Dann fügen Sie das except-Schlüsselwort zusammen mit der möglichen Ausnahme hinzu, gefolgt von beliebigem Code, der ausgeführt werden muss, wenn diese Bedingung eintritt. Da config.txt im System nicht enthalten ist, gibt Python aus, dass die Konfigurationsdatei nicht vorhanden ist. Der try- und except-Block verhindert zusammen mit einer hilfreichen Meldung eine Ablaufverfolgung und informiert den Benutzer dennoch über das Problem.

Obwohl eine nicht vorhandene Datei durchaus häufig vorkommt, ist dies nicht der einzige Fehler, den Sie finden können. Ungültige Dateiberechtigungen können das Lesen einer Datei verhindern, selbst wenn die Datei vorhanden ist. Erstellen Sie eine neue Python-Datei namens config.py in Visual Studio Code. Fügen Sie der Datei, die die Konfigurationsdatei des Navigationssystems findet und liest, den folgenden Code hinzu:

def main():
    try:
        configuration = open('config.txt')
    except FileNotFoundError:
        print("Couldn't find the config.txt file!")


if __name__ == '__main__':
    main()

Erstellen Sie dann ein Verzeichnis mit dem Namen config.txt. Versuchen Sie, die Datei config.py aufzurufen, um einen neuen Fehler anzuzeigen, der dem folgenden Fehler ähneln sollte:

python3 config.py
Traceback (most recent call last):
  File "/tmp/config.py", line 9, in <module>
    main()
  File "/tmp/config.py", line 3, in main
    configuration = open('config.txt')
IsADirectoryError: [Errno 21] Is a directory: 'config.txt'

Eine unbrauchbare Methode zur Behandlung dieses Fehlers wäre, alle möglichen Ausnahmen abzufangen, um eine Ablaufverfolgung zu verhindern. Um zu verstehen, warum das Abfangen aller Ausnahmen problematisch ist, versuchen Sie es, indem Sie die main()-Funktion in der neu erstellten config.py-Datei aktualisieren:

def main():
    try:
        configuration = open('config.txt')
    except Exception:
        print("Couldn't find the config.txt file!")

Führen Sie den Code nun an demselben Speicherort erneut aus, an dem das Verzeichnis config.txt mit nicht ordnungsgemäßen Berechtigungen vorhanden ist:

python3 config.py
Couldn't find the config.txt file!

Das Problem besteht nun darin, dass die Fehlermeldung falsch ist. Das Verzeichnis ist vorhanden, verfügt aber über andere Berechtigungen, und Python kann sie nicht lesen. Wenn Sie Softwarefehler beseitigen möchten, kann es frustrierend sein, Fehler zu haben, auf die Folgendes zutrifft:

  • Sie geben nicht an, was das eigentliche Problem ist.
  • Sie führen zu einer Ausgabe, die nicht mit dem tatsächlichen Problem übereinstimmt.
  • Sie geben keine Hinweise, was zum Beheben des Problems getan werden kann.

Korrigieren wir diesen Codeausschnitt, um all diese Ärgernisse zu beseitigen. Kehren Sie zum Abfangen von FileNotFoundError zurück, und fügen Sie dann einen weiteren except-Block hinzu, um PermissionError abzufangen:

def main():
    try:
        configuration = open('config.txt')
    except FileNotFoundError:
        print("Couldn't find the config.txt file!")
    except IsADirectoryError:
        print("Found config.txt but it is a directory, couldn't read it")

Führen Sie ihn nun an demselben Speicherort erneut aus, an dem das Verzeichnis config.txt vorhanden ist:

python3 config.py
Found config.txt but couldn't read it

Löschen Sie nun das Verzeichnis config.txt, um sicherzustellen, dass stattdessen der erste except-Block erreicht wird:

 rm -f config.txt
 python3 config.py
Couldn't find the config.txt file!

Wenn Fehler ähnlicher Natur sind und nicht einzeln behandelt werden müssen, können Sie die Ausnahmen in Klammern in der except-Zeile zu einer Gruppe zusammenfassen. Wenn z.B. das Navigationssystem stark ausgelastet ist und das Dateisystem überlastet wird, ist es sinnvoll, BlockingIOError und TimeOutError zusammen abzufangen:

def main():
    try:
        configuration = open('config.txt')
    except FileNotFoundError:
        print("Couldn't find the config.txt file!")
    except IsADirectoryError:
        print("Found config.txt but it is a directory, couldn't read it")
    except (BlockingIOError, TimeoutError):
        print("Filesystem under heavy load, can't complete reading configuration file")

Tipp

Auch wenn Sie Ausnahmen zusammenfassen können, sollten Sie nur dann so vorgehen, wenn es nicht erforderlich ist, sie einzeln zu behandeln. Vermeiden Sie es, viele Ausnahmen zu gruppieren, um eine generalisierte Fehlermeldung bereitzustellen.

Wenn Sie auf den Fehler zugreifen müssen, der der Ausnahme zugeordnet ist, müssen Sie die except-Zeile so aktualisieren, dass sie das as-Schlüsselwort enthält. Diese Technik eignet sich gut, wenn eine Ausnahme zu generisch ist und die Fehlermeldung nützlich sein kann:

try:
    open("mars.jpg")
except FileNotFoundError as err:
     print("Got a problem trying to read the file:", err)
Got a problem trying to read the file: [Errno 2] No such file or directory: 'mars.jpg'

In diesem Fall bedeutet as err, dass err zu einer Variablen mit dem Ausnahmeobjekt als Wert wird. Anschließend wird dieser Wert verwendet, um die Fehlermeldung auszugeben, die der Ausnahme zugeordnet ist. Ein weiterer Grund für die Verwendung dieser Technik ist der direkte Zugriff auf Attribute des Fehlers. Wenn Sie z. B. eine generischere OSError-Ausnahme abfangen, die die übergeordnete Ausnahme von FilenotFoundError und PermissionError ist, können Sie sie durch das .errno-Attribut voneinander unterscheiden:

try:
    open("config.txt")
except OSError as err:
     if err.errno == 2:
         print("Couldn't find the config.txt file!")
     elif err.errno == 13:
        print("Found config.txt but couldn't read it")
Couldn't find the config.txt file!

Versuchen Sie immer, die Technik zu verwenden, die die beste Lesbarkeit für den Code bietet und ihn in Zukunft gut verwaltbar macht. Manchmal ist es erforderlich, weniger gut lesbaren Code zu verwenden, um eine bessere Benutzererfahrung zu bieten, wenn ein Fehler auftritt.