Fehlerbehandlung und Ausnahmen

PythonPythonBeginner
Jetzt üben

This tutorial is from open-source community. Access the source code

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

Obwohl Ausnahmen früher eingeführt wurden, füllt dieser Abschnitt einige zusätzliche Details über die Fehlerprüfung und die Ausnahmebehandlung aus.

Dies ist ein Guided Lab, das schrittweise Anweisungen bietet, um Ihnen beim Lernen und Üben zu helfen. Befolgen Sie die Anweisungen sorgfältig, um jeden Schritt abzuschließen und praktische Erfahrungen zu sammeln. Historische Daten zeigen, dass dies ein Labor der Stufe Anfänger mit einer Abschlussquote von 85% ist. Es hat eine positive Bewertungsrate von 100% von den Lernenden erhalten.

Wie Programme fehlschlagen

Python führt keine Prüfung oder Validierung der Typen oder Werte von Funktionsargumenten durch. Eine Funktion funktioniert mit beliebigen Daten, die mit den Anweisungen in der Funktion kompatibel sind.

def add(x, y):
    return x + y

add(3, 4)               ## 7
add('Hello', 'World')   ## 'HelloWorld'
add('3', '4')           ## '34'

Wenn in einer Funktion Fehler auftreten, erscheinen sie zur Laufzeit (als Ausnahme).

def add(x, y):
    return x + y

>>> add(3, '4')
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +:
'int' and 'str'
>>>

Um den Code zu überprüfen, liegt ein starker Schwerpunkt auf der Tests (siehe später).

Ausnahmen

Ausnahmen werden verwendet, um Fehler zu signalisieren. Um selbst eine Ausnahme zu erhöhen, verwenden Sie die raise-Anweisung.

if name not in authorized:
    raise RuntimeError(f'{name} not authorized')

Um eine Ausnahme zu fangen, verwenden Sie try-except.

try:
    authenticate(username)
except RuntimeError as e:
    print(e)

Ausnahmebehandlung

Ausnahmen breiten sich bis zu der ersten passenden except aus.

def grok():
  ...
    raise RuntimeError('Whoa!')   ## Exception raised here

def spam():
    grok()                        ## Call that will raise exception

def bar():
    try:
       spam()
    except RuntimeError as e:     ## Exception caught here
      ...

def foo():
    try:
         bar()
    except RuntimeError as e:     ## Exception does NOT arrive here
      ...

foo()

Um die Ausnahme zu behandeln, legen Sie Anweisungen im except-Block fest. Sie können beliebige Anweisungen hinzufügen, um den Fehler zu behandeln.

def grok():...
    raise RuntimeError('Whoa!')

def bar():
    try:
      grok()
    except RuntimeError as e:   ## Exception caught here
        statements              ## Use this statements
        statements
     ...

bar()

Nach der Behandlung setzt die Ausführung mit der ersten Anweisung nach dem try-except fort.

def grok():...
    raise RuntimeError('Whoa!')

def bar():
    try:
      grok()
    except RuntimeError as e:   ## Exception caught here
        statements
        statements
     ...
    statements                  ## Resumes execution here
    statements                  ## And continues here
 ...

bar()

Einbaut Exceptions

Es gibt etwa zwei Dutzend eingebaut Exceptions. Normalerweise ist der Name der Exception anzeigend für das, was schiefgeht (z.B. eine ValueError wird ausgelöst, weil Sie einen schlechten Wert angegeben haben). Dies ist keine erschöpfende Liste. Überprüfen Sie die Dokumentation für mehr Informationen.

ArithmeticError
AssertionError
EnvironmentError
EOFError
ImportError
IndexError
KeyboardInterrupt
KeyError
MemoryError
NameError
ReferenceError
RuntimeError
SyntaxError
SystemError
TypeError
ValueError

Ausnahmewerte

Ausnahmen haben einen zugeordneten Wert. Er enthält genauere Informationen darüber, was schiefgeht.

raise RuntimeError('Invalid user name')

Dieser Wert ist Teil der Ausnahmeinstanz, die in die Variable eingefügt wird, die an except übergeben wird.

try:
 ...
except RuntimeError as e:   ## `e` hält die ausgelöste Ausnahme
 ...

e ist eine Instanz des Ausnahmetyps. Wenn es aber gedruckt wird, sieht es oft wie eine Zeichenkette aus.

except RuntimeError as e:
    print('Failed : Reason', e)

Das Fangen von mehreren Fehlern

Sie können verschiedene Arten von Ausnahmen mit mehreren except-Blöcken fangen.

try:
...
except LookupError as e:
...
except RuntimeError as e:
...
except IOError as e:
...
except KeyboardInterrupt as e:
...

Alternativ können Sie sie gruppieren, wenn die Anweisungen, um sie zu behandeln, dieselben sind:

try:
...
except (IOError,LookupError,RuntimeError) as e:
...

Das Fangen aller Fehler

Um jede Ausnahme zu fangen, verwenden Sie Exception wie folgt:

try:
...
except Exception:       ## GEFÄHR. Siehe unten
    print('An error occurred')

Im Allgemeinen ist es ein schlechter Gedanke, so etwas zu schreiben, da Sie nicht wissen, warum es fehlgeschlagen ist.

Falsche Art, Fehler zu fangen

Hier ist die falsche Art, Ausnahmen zu verwenden.

try:
    go_do_something()
except Exception:
    print('Computer says no')

Dies fängt alle möglichen Fehler ab und kann es unmöglich machen, das Problem zu debuggen, wenn der Code aus einem Grund fehlschlägt, den Sie gar nicht erwartet haben (z.B. ein nicht installierter Python-Modul usw.).

Ein etwas besseres Vorgehen

Wenn Sie alle Fehler fangen möchten, ist dies ein vernünftigerer Ansatz.

try:
    go_do_something()
except Exception as e:
    print('Computer sagt nein. Grund :', e)

Es meldet einen spezifischen Grund für das Versagen. Es ist fast immer ein guter Gedanke, einen Mechanismus zur Anzeige/Berichterstattung von Fehlern zu haben, wenn Sie Code schreiben, der alle möglichen Ausnahmen fängt.

Im Allgemeinen ist es jedoch besser, die Fehler so eng wie vernünftig zu fangen. Fangen Sie nur die Fehler, die Sie tatsächlich behandeln können. Lassen Sie andere Fehler passieren - vielleicht kann ein anderer Code sie behandeln.

Wiedererhöhung einer Ausnahme

Verwenden Sie raise, um einen gefangenen Fehler zu propagieren.

try:
    go_do_something()
except Exception as e:
    print('Computer sagt nein. Grund :', e)
    raise

Dies ermöglicht es Ihnen, Maßnahmen zu ergreifen (z.B. Protokollierung) und den Fehler an den Aufrufer weiterzugeben.

Ausnahmebest Practices

Fang keine Ausnahmen. Scheitere schnell und lautstark. Wenn es wichtig ist, wird jemand anders das Problem beheben. Fang nur eine Ausnahme, wenn Sie der jemand sind. Das heißt, fangen Sie nur Fehler, bei denen Sie sich erholen und vernünftig weitermachen können.

finally-Anweisung

Sie gibt an, welche Codezeilen unabhängig davon ausgeführt werden müssen, ob eine Ausnahme auftritt oder nicht.

lock = Lock()
...
lock.acquire()
try:
  ...
finally:
    lock.release()  ## Dies wird IMMER ausgeführt. Mit und ohne Ausnahme.

Wird häufig verwendet, um Ressourcen sicher zu verwalten (insbesondere Locks, Dateien usw.).

with-Anweisung

In modernem Code wird try-finally oft durch die with-Anweisung ersetzt.

lock = Lock()
with lock:
    ## lock acquired
  ...
## lock released

Ein vertrauteres Beispiel:

with open(filename) as f:
    ## Verwenden Sie die Datei
  ...
## Datei geschlossen

with definiert einen Verwendungskontext für eine Ressource. Wenn die Ausführung diesen Kontext verlässt, werden die Ressourcen freigegeben. with funktioniert nur mit bestimmten Objekten, die speziell programmiert wurden, um dies zu unterstützen.

Übung 3.8: Ausnahmen auslösen

Die parse_csv()-Funktion, die Sie im letzten Abschnitt geschrieben haben, ermöglicht die Auswahl von benutzerdefinierten Spalten, aber das funktioniert nur, wenn die Eingabedatei Spaltenüberschriften hat.

Ändern Sie den Code so, dass eine Ausnahme ausgelöst wird, wenn sowohl die Argumente select und has_headers=False übergeben werden. Beispielsweise:

>>> parse_csv('/home/labex/project/prices.csv', select=['name','price'], has_headers=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "fileparse.py", line 9, in parse_csv
    raise RuntimeError("select argument requires column headers")
RuntimeError: select argument requires column headers
>>>

Nachdem Sie diese eine Prüfung hinzugefügt haben, könnten Sie fragen, ob Sie in der Funktion andere Arten von Konsistenzprüfungen durchführen sollten. Beispielsweise sollten Sie überprüfen, ob der Dateiname ein String ist, ob types eine Liste ist oder etwas dergleichen?

Im Allgemeinen ist es normalerweise am besten, solche Tests zu überspringen und einfach zuzulassen, dass das Programm bei falschen Eingaben fehlschlägt. Die Fehlermeldung in der Stapelüberwachung wird auf die Quelle des Problems verweisen und kann beim Debuggen helfen.

Der Hauptgrund für das Hinzufügen der obigen Prüfung ist es, das Ausführen des Codes in einem unsinnigen Modus zu vermeiden (z.B. das Verwenden eines Features, das Spaltenüberschriften erfordert, aber gleichzeitig angibt, dass es keine Überschriften gibt).

Dies deutet auf einen Programmierfehler in dem aufrufenden Code hin. Das Überprüfen von Fällen, die "nicht passieren sollten", ist oft eine gute Idee.

✨ Lösung prüfen und üben

Übung 3.9: Ausnahmen fangen

Die von Ihnen geschriebene parse_csv()-Funktion wird verwendet, um den gesamten Inhalt einer Datei zu verarbeiten. In der Realität ist es jedoch möglich, dass Eingabedateien beschädigte, fehlende oder unsaubere Daten enthalten. Versuchen Sie dieses Experiment:

>>> portfolio = parse_csv('missing.csv', types=[str, int, float])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "fileparse.py", line 36, in parse_csv
    row = [func(val) for func, val in zip(types, row)]
ValueError: invalid literal for int() with base 10: ''
>>>

Ändern Sie die parse_csv()-Funktion, um alle ValueError-Ausnahmen, die während der Erstellung von Datensätzen generiert werden, zu fangen, und geben Sie eine Warnmeldung für Zeilen aus, die nicht konvertiert werden können.

Die Meldung sollte die Zeilennummer und Informationen über den Grund enthalten, warum der Konvertierungsversuch fehlschlug. Um Ihre Funktion zu testen, versuchen Sie, die obige Datei missing.csv zu lesen. Beispielsweise:

>>> portfolio = parse_csv('missing.csv', types=[str, int, float])
Zeile 4: Konvertierung von ['MSFT', '', '51.23'] nicht möglich
Zeile 4: Grund: invalid literal for int() with base 10: ''
Zeile 7: Konvertierung von ['IBM', '', '70.44'] nicht möglich
Zeile 7: Grund: invalid literal for int() with base 10: ''
>>>
>>> portfolio
[{'name': 'AA','shares': 100, 'price': 32.2}, {'name': 'IBM','shares': 50, 'price': 91.1}, {'name': 'CAT','shares': 150, 'price': 83.44}, {'name': 'GE','shares': 95, 'price': 40.37}, {'name': 'MSFT','shares': 50, 'price': 65.1}]
>>>
✨ Lösung prüfen und üben

Übung 3.10: Fehler unterdrücken

Ändern Sie die parse_csv()-Funktion so, dass Fehler bei der Analyse unterdrückt werden können, wenn der Benutzer dies explizit wünscht. Beispielsweise:

>>> portfolio = parse_csv('missing.csv', types=[str,int,float], silence_errors=True)
>>> portfolio
[{'name': 'AA','shares': 100, 'price': 32.2}, {'name': 'IBM','shares': 50, 'price': 91.1}, {'name': 'CAT','shares': 150, 'price': 83.44}, {'name': 'GE','shares': 95, 'price': 40.37}, {'name': 'MSFT','shares': 50, 'price': 65.1}]
>>>

Das Fehlerhandeln ist eine der schwierigsten Dinge, die man in den meisten Programmen richtig machen muss. Im Allgemeinen sollten Sie Fehler nicht stillschweigend ignorieren. Stattdessen ist es besser, Probleme zu melden und dem Benutzer die Möglichkeit zu geben, die Fehlermeldung zu unterdrücken, wenn er dies wählt.

✨ Lösung prüfen und üben

Zusammenfassung

Herzlichen Glückwunsch! Sie haben das Lab zu Fehlerprüfung abgeschlossen. Sie können in LabEx weitere Labs absolvieren, um Ihre Fähigkeiten zu verbessern.