Python Debugging

PythonBeginner
Jetzt üben

Einführung

In der Computerprogrammierung ist das Debugging der wesentliche Prozess des Auffindens und Behebens von Fehlern oder „Bugs“, die verhindern, dass ein Programm korrekt funktioniert. Obwohl Fehler ein unvermeidlicher Bestandteil der Softwareentwicklung sind, bietet Python eine leistungsstarke Reihe von Werkzeugen, die Ihnen helfen, diese effizient zu diagnostizieren und zu beheben.

In diesem Lab lernen Sie mehrere grundlegende Debugging-Techniken in Python kennen. Wir werden behandeln, wie man:

  • Ausnahmen manuell auslöst und behandelt, um den Programmfluss bei Fehlern zu steuern.
  • Detaillierte Fehler-Tracebacks als Zeichenketten (Strings) für Protokollierungszwecke erfasst.
  • Assertions (Zusicherungen) als Plausibilitätsprüfungen verwendet, um logische Fehler frühzeitig abzufangen.
  • Ein flexibles Protokollierungssystem (Logging System) implementiert, um die Ausführung Ihres Programms zu verfolgen und Probleme zu diagnostizieren, ohne dessen Ablauf zu unterbrechen.

Auslösen von Ausnahmen (Raising Exceptions)

In diesem Schritt lernen Sie, wie Sie mit der Anweisung raise manuell Ausnahmen auslösen. Ausnahmen dienen dazu, das Auftreten eines Fehlers zu signalisieren. Eine raise-Anweisung besteht aus dem Schlüsselwort raise, einem Aufruf der Funktion Exception() und einer hilfreichen Fehlermeldungszeichenkette, die an Exception() übergeben wird.

Es ist gängige Praxis, Funktionen Ausnahmen auszulösen, wenn deren Vorbedingungen nicht erfüllt sind, und diese Ausnahmen dann im aufrufenden Code mithilfe von try- und except-Blöcken zu behandeln.

Hinweis: Wenn Sie mit der WebIDE (ähnlich VS Code) nicht vertraut sind, lernen Sie diese zuerst in diesem Lab.

Versuchen wir dies anhand eines Beispiels.

  1. Öffnen Sie die Datei raise_exception.py im Dateiexplorer auf der linken Seite.
  2. Fügen Sie den folgenden Code in die Datei ein. Dieser Code definiert eine Funktion box_print, die Ausnahmen für ungültige Argumente auslöst, und ruft diese Funktion anschließend mit gültigen und ungültigen Eingaben auf, wobei die Ausnahmen abgefangen werden.
def box_print(symbol, width, height):
    if len(symbol) != 1:
      raise Exception('Symbol must be a single character string.')
    if width <= 2:
      raise Exception('Width must be greater than 2.')
    if height <= 2:
      raise Exception('Height must be greater than 2.')
    print(symbol * width)
    for i in range(height - 2):
        print(symbol + (' ' * (width - 2)) + symbol)
    print(symbol * width)

## Handle exceptions when calling the function
for sym, w, h in (('*', 4, 4), ('O', 20, 5), ('x', 1, 3), ('ZZ', 3, 3)):
    try:
        box_print(sym, w, h)
    except Exception as err:  ## Catch exception and print error message
        print('An exception happened: ' + str(err))
  1. Führen Sie nun das Skript im Terminal aus, um zu sehen, wie die Ausnahmen ausgelöst und behandelt werden.
python3 raise_exception.py

Sie sehen die folgende Ausgabe. Die ersten beiden Aufrufe von box_print sind erfolgreich, während die letzten beiden fehlschlagen und den except-Block auslösen, um die benutzerdefinierten Fehlermeldungen auszugeben.

****
*  *
*  *
****
OOOOOOOOOOOOOOOOOOOO
O                  O
O                  O
O                  O
OOOOOOOOOOOOOOOOOOOO
An exception happened: Width must be greater than 2.
An exception happened: Symbol must be a single character string.

Den Traceback als String erhalten

Wenn eine unbehandelte Ausnahme (Exception) auftritt, gibt Python einen Traceback aus, der ein detaillierter Bericht über die Funktionsaufrufe ist, die zum Fehler geführt haben. Manchmal möchten Sie eine Ausnahme elegant behandeln, aber dennoch den vollständigen Traceback für eine spätere Fehlerbehebung aufzeichnen. Das Modul traceback ermöglicht Ihnen dies.

In diesem Schritt lernen Sie, wie Sie traceback.format_exc() verwenden, um den Traceback als Zeichenkette (String) zu erfassen und in einer Datei zu speichern.

  1. Öffnen Sie die Datei get_traceback.py im Dateiexplorer.
  2. Fügen Sie den folgenden Code hinzu. Dieses Skript importiert das Modul traceback und verwendet dann einen try...except-Block, um eine Ausnahme abzufangen. Innerhalb des except-Blocks schreibt es den vollständigen Traceback in eine Datei namens errorInfo.txt.
import traceback

try:
    raise Exception('This is the error message.')
except:
    with open('errorInfo.txt', 'w') as error_file:
        error_file.write(traceback.format_exc())  ## Write traceback to file
    print('The traceback info was written to errorInfo.txt.')
  1. Führen Sie das Skript im Terminal aus.
python3 get_traceback.py

Das Skript gibt eine Bestätigungsnachricht aus:

The traceback info was written to errorInfo.txt.
  1. Überprüfen Sie nun den Inhalt der neu erstellten Datei errorInfo.txt. Sie können sie entweder über den Dateiexplorer öffnen oder den Befehl cat im Terminal verwenden.
cat errorInfo.txt

Die Datei enthält den vollständigen Traceback, ähnlich dem folgenden:

Traceback (most recent call last):
  File "/home/labex/project/get_traceback.py", line 4, in <module>
    raise Exception('This is the error message.')
Exception: This is the error message.

Diese Technik ist äußerst nützlich, um Fehler in Produktionsanwendungen zu protokollieren, ohne das Programm abstürzen zu lassen.

Verwendung von Assertions (Zusicherungen)

Assertions (Zusicherungen) sind Plausibilitätsprüfungen, um sicherzustellen, dass Ihr Code nichts offensichtlich Falsches tut. Eine assert-Anweisung prüft, ob eine Bedingung wahr ist. Wenn die Bedingung falsch ist, löst sie einen AssertionError aus, der idealerweise Ihr Programm zum Absturz bringen sollte. Dieser "Fail-Fast"-Ansatz hilft Ihnen, die Wurzel eines Fehlers schneller zu identifizieren.

Eine assert-Anweisung besteht aus dem Schlüsselwort assert, einer zu prüfenden Bedingung und einer optionalen Fehlermeldung.

In diesem Schritt verwenden Sie eine Assertion, um den Zustand eines Programms zu validieren.

  1. Öffnen Sie die Datei use_assertions.py im Dateiexplorer.
  2. Fügen Sie den folgenden Code hinzu. Er überprüft den Status von pod_bay_door_status. Die erste Assertion wird stillschweigend bestanden, aber die zweite schlägt fehl und löst einen AssertionError aus.
## assert statement: check condition, raise AssertionError if False
pod_bay_door_status = 'open'
assert pod_bay_door_status == 'open', 'The pod bay doors need to be "open".'  ## Passes

pod_bay_door_status = 'I\'m sorry, Dave. I\'m afraid I can\'t do that.'
assert pod_bay_door_status == 'open', 'The pod bay doors need to be "open".'  ## Raises AssertionError
  1. Führen Sie das Skript im Terminal aus.
python3 use_assertions.py

Da die zweite Assertion fehlschlägt, stürzt das Programm ab, und Sie sehen einen AssertionError mit Ihrer benutzerdefinierten Meldung.

Traceback (most recent call last):
  File "/home/labex/project/use_assertions.py", line 6, in <module>
    assert pod_bay_door_status == 'open', 'The pod bay doors need to be "open".'
AssertionError: The pod bay doors need to be "open".

Im Gegensatz zu Ausnahmen sollten Sie Assertions nicht mit try...except behandeln. Ein AssertionError signalisiert einen Fehler im Programm, der behoben werden muss. Es ist auch erwähnenswert, dass Assertions global deaktiviert werden können, indem Python mit dem Flag -O (optimize) ausgeführt wird. Daher sollten sie nicht für die Eingabevalidierung verwendet werden, die immer erzwungen werden muss.

Logging implementieren

Während print()-Anweisungen zum Debuggen verwendet werden können, ist der leistungsfähigere und flexiblere Ansatz die Verwendung des in Python integrierten logging-Moduls. Logging ermöglicht es Ihnen, Ereignisse während der Programmausführung zu verfolgen und Nachrichten nach Schweregrad zu kategorisieren.

In diesem Schritt fügen Sie einem Programm Logging hinzu, um bei der Diagnose eines Fehlers zu helfen.

  1. Öffnen Sie zuerst die Datei factorial_log.py im Dateiexplorer.
  2. Fügen Sie den folgenden Code hinzu. Dieses Programm versucht, die Fakultät (factorial) einer Zahl zu berechnen, enthält aber einen Fehler. Wir haben logging.debug()-Aufrufe hinzugefügt, um die Werte der Variablen bei jedem Schritt zu verfolgen. Die Funktion logging.basicConfig() konfiguriert das Logging-System so, dass Nachrichten der Stufe DEBUG und höher angezeigt werden.
import logging
logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s')
logging.debug('Start of program')

def factorial(n):
    logging.debug('Start of factorial(%s)' % (n))
    total = 1
    for i in range(1, n + 1):
        total *= i
        logging.debug('i is ' + str(i) + ', total is ' + str(total))
    logging.debug('End of factorial(%s)' % (n))
    return total

print(factorial(5))
logging.debug('End of program')
  1. Führen Sie das Skript im Terminal aus.
python3 factorial_log.py

Sie sehen detaillierte Log-Ausgaben, die mit der regulären Ausgabe des Programms durchsetzt sind.

 %(asctime)s - DEBUG - Start of program
 %(asctime)s - DEBUG - Start of factorial(5)
 %(asctime)s - DEBUG - i is 1, total is 1
 %(asctime)s - DEBUG - i is 2, total is 2
 %(asctime)s - DEBUG - i is 3, total is 6
 %(asctime)s - DEBUG - i is 4, total is 24
 %(asctime)s - DEBUG - i is 5, total is 120
 %(asctime)s - DEBUG - End of factorial(5)
120
 %(asctime)s - DEBUG - End of program

Durch die Untersuchung der Log-Nachrichten können Sie den Wert von i und total in jeder Iteration der Schleife klar erkennen, was eine leistungsstarke Methode ist, um den Programmablauf zu verstehen und Fehler zu finden.

Logging-Verhalten konfigurieren

Das logging-Modul ist hochgradig konfigurierbar. Sie können die Schweregradstufe der anzuzeigenden Nachrichten steuern, das Logging vollständig deaktivieren oder Log-Nachrichten an eine Datei anstatt an den Bildschirm umleiten.

Logging-Stufen (Logging Levels)

Es gibt fünf Standard-Logging-Stufen, jede mit einer entsprechenden Funktion:

Stufe Logging-Funktion Beschreibung
DEBUG logging.debug() Für kleine Details, nützlich nur bei der Diagnose von Problemen.
INFO logging.info() Zur Aufzeichnung allgemeiner Ereignisse oder zur Bestätigung, dass Dinge wie erwartet funktionieren.
WARNING logging.warning() Um auf ein potenzielles Problem hinzuweisen, das das Programm nicht stoppt.
ERROR logging.error() Zur Aufzeichnung eines Fehlers, der zum Fehlschlagen einer bestimmten Operation geführt hat.
CRITICAL logging.critical() Für einen fatalen Fehler, der kurz davor steht, die Beendigung des Programms zu verursachen.

Wenn Sie eine Logging-Stufe in basicConfig festlegen, zeigt Python Nachrichten auf dieser Stufe und allen höheren Stufen an. Wenn Sie beispielsweise level=logging.INFO festlegen, werden INFO, WARNING, ERROR und CRITICAL Nachrichten angezeigt, DEBUG jedoch nicht.

Deaktivieren des Loggings

Nach dem Debuggen möchten Sie möglicherweise alle Log-Nachrichten ausschalten, ohne die logging-Aufrufe aus Ihrem Code zu entfernen. Dies können Sie mit logging.disable() tun. Es nimmt eine Stufe als Argument entgegen und deaktiviert alle Log-Nachrichten auf dieser Stufe und darunter. Zum Beispiel unterdrückt logging.disable(logging.CRITICAL) jede Log-Nachricht.

Logging in eine Datei

Anstatt Logs auf der Konsole auszugeben, können Sie sie in einer Datei speichern, indem Sie das Schlüsselwortargument filename an logging.basicConfig() übergeben.

Üben wir dies.

  1. Öffnen Sie die Datei configure_logging.py.
  2. Fügen Sie den folgenden Code hinzu. Er konfiguriert das Logging so, dass alle Nachrichten der Stufe DEBUG und höher in eine Datei namens myProgramLog.txt geschrieben werden.
import logging

## Configure logging to write to a file
logging.basicConfig(filename='myProgramLog.txt', level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

logging.debug('This is a debug message.')
logging.info('This is an info message.')
logging.warning('This is a warning message.')
  1. Führen Sie das Skript im Terminal aus.
python3 configure_logging.py

Dieses Mal sehen Sie keine Ausgabe im Terminal.

  1. Überprüfen Sie den Inhalt der Datei myProgramLog.txt.
cat myProgramLog.txt

Sie werden sehen, dass alle Log-Nachrichten mit Zeitstempeln und Schweregraden in die Datei geschrieben wurden.

2023-10-27 10:30:00,123 - DEBUG - This is a debug message.
2023-10-27 10:30:00,123 - INFO - This is an info message.
2023-10-27 10:30:00,123 - WARNING - This is a warning message.

Dies beendet unsere Einführung in grundlegende Python-Debugging-Techniken.

Zusammenfassung

In diesem Lab haben Sie mehrere wesentliche Python-Debugging-Techniken gelernt und geübt. Sie begannen damit, die raise-Anweisung zu verwenden, um benutzerdefinierte Ausnahmen (Exceptions) zu erstellen und auszulösen, und diese mit try...except-Blöcken zu behandeln. Anschließend lernten Sie, wie man einen vollständigen Ausnahme-Traceback als String mithilfe von traceback.format_exc() für Logging-Zwecke erfasst.

Danach haben Sie assert-Anweisungen als Mittel zur Durchführung von Plausibilitätsprüfungen (Sanity Checks) und zum "schnellen Fehlschlagen" (fail fast) untersucht, wenn Ihr Programm in einen unerwarteten Zustand gerät. Schließlich haben Sie sich mit dem logging-Modul beschäftigt und gelernt, wie man es konfiguriert, um Nachrichten anzuzeigen, die Ausgabe mit verschiedenen Logging-Stufen zu steuern und Logs zur dauerhaften Speicherung in eine Datei zu schreiben.

Diese Fähigkeiten sind grundlegend für das Schreiben robuster, wartbarer und leichter zu debuggender Python-Anwendungen.