Fehleranalyse

IPython enthält verschiedene Werkzeuge, um fehlerhaften Code zu analysieren, im Wesentlichen das Exception Reporting und den Debugger.

Exceptions kontrollieren mit %xmode

Wenn die Ausführung eines Python-Skripts fehlschlägt, wird meist eine sog. Exception ausgelöst und relevante Informationen zur Fehlerursache in einen Traceback geschrieben. Mit der %xmode-Magic-Funktion könnt ihr in IPython die Menge der Informationen steuern, die euch angezeigt werden. Betrachten wir hierfür den folgenden Code:

[1]:
def func1(a, b):
    return a / b

def func2(x):
    a = x
    b = x - 1
    return func1(a, b)
[2]:
func2(1)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-2-7cb498ea7ed1> in <module>
----> 1 func2(1)

<ipython-input-1-586ccabd0db3> in func2(x)
      5     a = x
      6     b = x - 1
----> 7     return func1(a, b)

<ipython-input-1-586ccabd0db3> in func1(a, b)
      1 def func1(a, b):
----> 2     return a / b
      3
      4 def func2(x):
      5     a = x

ZeroDivisionError: division by zero

Der Aufruf von func2führt zu einem Fehler, und der Traceback zeigt genau, was passiert ist: in jeder Zeile wird der Kontext jedes Schritts angezeigt, der schließlich zum Fehler geführt hat. Mit der %xmode-Magic-Funktion (kurz für Exception-Modus) können wir steuern, welche Informationen uns angezeigt werden sollen.

%xmode nimmt ein einziges Argument, den Modus, und es gibt drei Möglichkeiten: * Plain * Context * Verbose

Die Standardeinstellung ist Context und gibt eine Ausgabe wie die obige aus. Plain ist kompakter und liefert weniger Informationen:

[3]:
%xmode Plain
func2(1)
Exception reporting mode: Plain
Traceback (most recent call last):

  File "<ipython-input-3-2c0c65acd0a8>", line 2, in <module>
    func2(1)

  File "<ipython-input-1-586ccabd0db3>", line 7, in func2
    return func1(a, b)

  File "<ipython-input-1-586ccabd0db3>", line 2, in func1
    return a / b

ZeroDivisionError: division by zero

Der Verbose-Modus zeigt einige zusätzliche Informationen an, einschließlich der Argumente für alle aufgerufenen Funktionen:

[4]:
%xmode Verbose
func2(1)
Exception reporting mode: Verbose
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-4-180acea4108b> in <module>
      1 get_ipython().run_line_magic('xmode', 'Verbose')
----> 2 func2(1)
        global func2 = <function func2 at 0x106312a60>

<ipython-input-1-586ccabd0db3> in func2(x=1)
      5     a = x
      6     b = x - 1
----> 7     return func1(a, b)
        global func1 = <function func1 at 0x1063129d8>
        a = 1
        b = 0

<ipython-input-1-586ccabd0db3> in func1(a=1, b=0)
      1 def func1(a, b):
----> 2     return a / b
        a = 1
        b = 0
      3
      4 def func2(x):
      5     a = x

ZeroDivisionError: division by zero

Diese zusätzlichen Informationen können helfen, den Grund für die Exception einzugrenzen. Umgekehrt kann der Verbose-Modus jedoch bei komplexem Code zu extrem langen Tracebacks führen, bei denen kaum noch die wesentlichen Stellen erkannt werden können.

Debugging

Wenn durch das Lesen eines Traceback ein Fehler nicht gefunden werden kann, hilft Debugging weiter. Der Python-Standard für interaktives Debugging ist der Python-Debugger pdb. Mit ihm könnt ihr euch zeilenweise durch den Code navigieren, um festzustellen, was möglicherweise einen Fehler verursacht. Die erweiterte Version für IPython ist ipdb.

In IPython ist der %debug-Magic-Befehl möglicherweise die bequemste Art zum Debugging. Wenn ihr ihn aufruft, nachdem eine Exception ausgegeben wurde, wird automatisch ein interaktiver Debug-Prompt während der Exception geöffnet. Mit der ipdb-Eingabeaufforderung könnt ih den aktuellen Status des Stacks untersuchen, die verfügbaren Variablen untersuchen und sogar Python-Befehle ausführen.

Schauen wir uns die letzte Exception an und führen dann einige grundlegende Aufgaben aus:

[5]:
%debug
> <ipython-input-1-586ccabd0db3>(2)func1()
      1 def func1(a, b):
----> 2     return a / b
      3 
      4 def func2(x):
      5     a = x

ipdb> print(a)
1
ipdb> print(b)
0
ipdb> quit

Der interaktive Debugger bietet jedoch viel mehr – wir können im Stack auch auf und ab gehen und die Werte von Variablen untersuchen:

[6]:
%debug
> <ipython-input-1-586ccabd0db3>(2)func1()
      1 def func1(a, b):
----> 2     return a / b
      3 
      4 def func2(x):
      5     a = x

ipdb> up
> <ipython-input-1-586ccabd0db3>(7)func2()
      3 
      4 def func2(x):
      5     a = x
      6     b = x - 1
----> 7     return func1(a, b)

ipdb> print(x)
1
ipdb> up
> <ipython-input-4-180acea4108b>(2)<module>()
      1 get_ipython().run_line_magic('xmode', 'Verbose')
----> 2 func2(1)

ipdb> down
> <ipython-input-1-586ccabd0db3>(7)func2()
      3 
      4 def func2(x):
      5     a = x
      6     b = x - 1
----> 7     return func1(a, b)

ipdb> quit

Dies vereinfacht die Suche nach den Funktionsaufrufen, die zum Fehler geführt haben, enorm.

Wenn der Debugger automatisch gestartet werden soll, wenn eine Ausnahme ausgelöst wird, könnt ihr die %pdb-Magic-Funktion verwenden, um dieses Verhalten zu aktivieren:

[7]:
%xmode Plain
%pdb on
func2(1)
Exception reporting mode: Plain
Automatic pdb calling has been turned ON
Traceback (most recent call last):

  File "<ipython-input-7-f80f6b5cecf3>", line 3, in <module>
    func2(1)

  File "<ipython-input-1-586ccabd0db3>", line 7, in func2
    return func1(a, b)

  File "<ipython-input-1-586ccabd0db3>", line 2, in func1
    return a / b

ZeroDivisionError: division by zero

> <ipython-input-1-586ccabd0db3>(2)func1()
      1 def func1(a, b):
----> 2     return a / b
      3 
      4 def func2(x):
      5     a = x

ipdb> print(b)
0
ipdb> quit

Wenn ihr ein Skript habt, das ihr von Anfang an im interaktiven Modus ausführen möchtet, so könnt ihr dies mit dem Befehl %run -d.

Wesentliche Befehle des ipdb

Befehl

Beschreibung

list

Zeige den aktuellen Ort in der Datei

h(elp)

Liste der Befehle anzeigen oder Hilfe zu einem bestimmten Befehl suchen

q(uit)

Beendet den Debugger und das Programm

c(ontinue)

Den Debugger beenden, im Programm fortfahren

n(ext)

Gehe zum nächsten Schritt des Programms

<enter>

Wiederhole den vorherigen Befehl

p(rint)

Druckvariablen

s(tep)

Schritt in eine Unterroutine

r(eturn)

Rückkehr aus einem Unterprogramm

Weitere Informationen zum IPython-Debugger erhaltet ihr unter ipdb.