Das Python-Modul pdb stellt einen interaktiven Debugger für Python-Programme zur Verfügung. Es umfasst den Quelltext in Einzelschritten zu evaluieren, Variablen zu inspizieren, beliebigen Python-Code an jeder Stelle auszuführen und das sogenannte “post-mortem debugging”.
Debugger Starten
Frei nach dem Motto: “viele Wege führen nach Rom” – gibt es verschiedene Wege den Debugger zu starten. Je nach Bedarf, wo und was zu debuggen ist, entscheidet der Benutzer, wann der Python-Interpreter den Debugger “betritt”.
Von der Kommandozeile
Die Eingabeaufforderung des Debuggers ist (Pdb).
Rufen wir den Debugger von der Kommandozeile aus auf, lädt dieser unser Skript pdb_test.py und stoppt bei der ersten Anweisung:

$ python -m pdb pdb_test.py
> .../pdb_test.py(3)()
-> def test():
(Pdb)

Im Interpreter
Experimentiert man im interaktiven Interpreter, ist es hilfreich den Debugger auch hier via pdb.run oder pdb.runeval starten zu können:

$ python
Python 2.7.2 (default, Oct 27 2011, 01:40:22)
[GCC 4.6.1 20111003 (Red Hat 4.6.1-10)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pdb
>>> import pdb_test
>>> pdb.run('pdb_test.test()')
> <string>(1)<module>()
(Pdb)

Im Programm
Die oben genannten Möglichkeiten sind in den meisten Fällen nur dann hilfreich, wenn das entwickelte Programm überschaubar ist oder Probleme schon am Anfang der Ausführung auftreten. Stößt man auf Fehler in größeren Programmen oder erst später in der Ausführung, ist es von Vorteil, den Debugger im Programm zu starten:

#!/usr/bin/env python
import pdb
def test():
    pdb.set_trace()
    print "Hallo!"
if __name__ == '__main__':
    test()

Der Funktionsaufruf pdb.set_trace() in Zeile 6 startet den Debugger, wenn die Instruktion erreicht wird:

$ python pdb_test.py
> .../pdb_test.py(7)test()
-> print "Hallo!"
(Pdb)

Nach einem Fehler
Programme nach einem Fehler zu debuggen, nennt sich “post-mortem debugging”. pdb unterstützt das durch die Funktionen pm() und post_mortem().

class Output(object):
    def __init__(self, output):
        self.the_output = output
    def echo(self):
        print self.output

In Zeile 7 versteckt sich ein AttributeError, da wir eigentlich self.the_output ausgeben müssten. Wird die besagte Zeile aufgerufen, stoppt die Ausführung mit dem entsprechenden Fehler. Danach können wir pdb.pm() aufrufen, um den Debugger an der Stelle zu starten, an der die Exception aufgetreten ist:

$ python
Python 2.7.2 (default, Oct 27 2011, 01:40:22)
[GCC 4.6.1 20111003 (Red Hat 4.6.1-10)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pdb_test
>>> pdb_test.Output("Hallo!").echo()
Traceback (most recent call last):
  File "", line 1, in 
  File "pdb_test.py", line 11, in echo
    print self.output
AttributeError: 'Output' object has no attribute 'output'
>>> import pdb
>>> pdb.pm()
> .../pdb_test.py(11)echo()
-> print self.output
(Pdb)

Debugger benutzen
Der Debugger versteht einen definierten Satz an Befehlen – alle anderen Anweisungen werden als Python-Code behandelt und im Kontext des Programm ausgeführt. So können sogar Variablen und Funktionen während dem Debugging verändert werden. Die Eingabe einer leeren Zeile wiederholt den letzten eingegeben Befehl.
Navigation
w(here): Anzeigen der Position im call stack und nächste auszuführende Instruktion:

(Pdb) where
  /usr/lib64/python2.7/bdb.py(387)run()
-> exec cmd in globals, locals
  (1)()
> .../pdb_test.py(3)()
-> import pdb

l(ist) [start[, ende]]: Auruf ohne Argumente zeigt 5 Zeilen vor und nach der aktuellen Zeile. Mit einem Argument werden 5 Zeilen vor und nach der angegebenen Zeile angezeigt und mit 2 Argumenten der angegebene Bereich:

(Pdb) list
  1  	#!/usr/bin/env python
  2
  3  ->	import pdb
  4
  5  	class Output(object):
  6
  7  	    def __init__(self, output):
  8  	        self.the_output = output
  9
 10  	    def echo(self):
 11  	        print self.output
(Pdb) l 13
  8  	        self.the_output = output
  9
 10  	    def echo(self):
 11  	        print self.output
 12
 13  	if __name__ == '__main__':
 14  	    Output("Hallo!").echo()
[EOF]
(Pdb) l 5, 14
  5  	class Output(object):
  6
  7  	    def __init__(self, output):
  8  	        self.the_output = output
  9
 10  	    def echo(self):
 11  	        print self.output
 12
 13  	if __name__ == '__main__':
 14  	    Output("Hallo!").echo()

u(p), d(own): Aktuelle frame im call stack nach oben (up, älter) oder unten (down, neuer) verschieben:

(Pdb) up
> .../pdb_test.py(14)()
-> Output("Hallo!").echo()
(Pdb) d
> .../pdb_test.py(11)echo()
-> print self.output

Schrittweise Ausfhüren
s(step): Führt die aktuelle Zeile aus und stoppt bei der nächsten aufzurufenden Funktion oder Zeile in der aktuellen Sequenz:

(Pdb) ...
> .../pdb_test.py(14)()
-> Output("Hallo!").echo()
(Pdb) s
--Call--
> .../pdb_test.py(7)__init__()
-> def __init__(self, output):

n(ext): Verhält sich ähnlich wie step, stoppt aber nicht bei Funktionen die von der auszuführenden Anweisung aufgerufen werden:

(Pdb) ...
> .../pdb_test.py(14)()
-> Output("Hallo!").echo()
(Pdb) n
AttributeError: "'Output' object has no attribute 'output'"
> /tmp/pdb_test.py(14)()
-> Output("Hallo!").echo()

r(eturn): Stoppt bei der nächsten return-Anweisung:

#!/usr/bin/env/python
import random
def randint(a, b):
    random_int = random.randint(a, b)
    return random_int
if __name__ == '__main__':
    random.seed()
    print randint(0, 100)
(Pdb) ...
> .../pdb_return.py(11)()
-> print randint(0, 100)
(Pdb) s
--Call--
> .../pdb_return.py(5)randint()
-> def randint(a, b):
(Pdb) r
--Return--
> .../pdb_return.py(7)randint()->58
-> return random_int

Variablen überprüfen
a(rgs): Zeigt die Argumente der aktuellen Funktion und
p expression: Evaluiert expression und zeigt dessen Ausgabe, zum Beispiel den Wert einer Variable:

(Pdb) ...
> .../pdb_return.py(11)()
-> print randint(0, 100)
(Pdb) s
--Call--
> .../pdb_return.py(5)randint()
-> def randint(a, b):
(Pdb) r
--Return--
> .../pdb_return.py(7)randint()->24
-> return random_int
(Pdb) args
a = 0
b = 100
(Pdb) p random_int
24

Natürlich unterstützt der Debugger auch Breakpoints und jump – dazu schreibe ich vielleicht etwas in einem zweiten Teil.
Weiterführende Informationen sind in der Python Dokumentation und den absolut genialen Abhandlungen von Doug Hellmann, namens Python Module of the Week, zu finden, dessen Gliederung ich hier mal ganz frei übernommen habe.

Eric Lippmann
Eric Lippmann
Lead Senior Developer

Eric kam während seines ersten Lehrjahres zu NETWAYS und hat seine Ausbildung bereits 2011 sehr erfolgreich abgeschlossen. Seit Beginn arbeitet er in der Softwareentwicklung und dort an den unterschiedlichen NETWAYS Open Source Lösungen, insbesondere inGraph und im Icinga Team an Icinga Web. Darüber hinaus zeichnet er sich für viele Kundenentwicklungen in der Finanz- und Automobilbranche verantwortlich.