Interactive Debugging in Python
by Jeremy Jones09/01/2005
Programmers typically place debuggers in the "uh oh" corner of their
toolboxes, somewhere between the network packet sniffer and the disassembler.
The only time we reach for a debugger is when something goes wrong or breaks
and our standard debugging techniques such as print statements (or better
yet, log messages) do not reveal the root of the problem. The Python standard
library contains an interactive source code debugger which suits "uh oh"
situations well.
The Python interactive source code debugger runs code in a controlled manner. It allows stepping through a piece of code one line at a time, walking up and back down call trees, setting break points, and using the power of the Python shell for various levels of introspection and control.
What's the big deal? I can do most of that by modifying the source code by
placing print statements everywhere (mostly resembling either
print "dir>>", dir() or print "variable_name>>",
variable_name) and running it again. While this is true, there are
issues of convenience and control.
|
Related Reading
Python Cookbook |
Regarding convenience, sometimes it is much more convenient to drop in to a
debugger to see what is going on right in front of your eyes and poke at your
code while at a Python prompt rather than having to modify the code and rerun
it. What if you are trying to debug a database application in which the bug occurs after retrieving a set of data that took tens of seconds to retrieve?
Worse still, what if you have a bug in a computationally intense application
that occurs after processing several hours' worth of data? You might possibly
nearly break even on the first run of a program using either the interactive
debugger versus the print technique of debugging. But chances are
you will not have gathered enough data on the first run to solve the problem
successfully. The payback comes when it would have taken several runs and
multiple print inserts into the source code to solve the problem.
With the debugger, you can do an exhaustive amount of information gathering and
analysis and, hopefully, solve the problem all at once.
Regarding control, which overlaps with convenience, debugging an application at a prompt, as opposed to modifying source code and rerunning it, provides an immediate level of control. Sometimes it is easier to figure out what is going on with a set of code if you have live objects at your fingertips and can interact with them through a prompt, especially if you are using a powerful shell such as IPython. This is one of the minor general reasons Python is a powerful language; the interactive prompt provides immediate, interactive control over objects living in a set of code.
Debugger Module Contents
The pdb module contains the debugger. pdb contains
one class, Pdb, which inherits from bdb.Bdb. The
debugger documentation mentions six functions, which create an interactive
debugging session:
pdb.run(statement[, globals[, locals]])
pdb.runeval(expression[, globals[, locals]])
pdb.runcall(function[, argument, ...])
pdb.set_trace()
pdb.post_mortem(traceback)
pdb.pm()
All six functions provide a slightly different mechanism for dropping a user into the debugger.
pdb.run(statement[, globals[, locals]])
pdb.run() executes the string statement under the
debugger's control. Global and local dictionaries are optional parameters:
#!/usr/bin/env python
import pdb
def test_debugger(some_int):
print "start some_int>>", some_int
return_int = 10 / some_int
print "end some_int>>", some_int
return return_int
if __name__ == "__main__":
pdb.run("test_debugger(0)")
pdb.runeval(expression[,
globals[, locals]])
pdb.runeval() is identical to pdb.run(), except
that pdb.runeval() returns the value of the evaluated string
expression:
#!/usr/bin/env python
import pdb
def test_debugger(some_int):
print "start some_int>>", some_int
return_int = 10 / some_int
print "end some_int>>", some_int
return return_int
if __name__ == "__main__":
pdb.runeval("test_debugger(0)")
pdb.runcall(function[,
argument, ...])
pdb.runcall() calls the specified function and
passes any specified arguments to it:
#!/usr/bin/env python
import pdb
def test_debugger(some_int):
print "start some_int>>", some_int
return_int = 10 / some_int
print "end some_int>>", some_int
return return_int
if __name__ == "__main__":
pdb.runcall(test_debugger, 0)
pdb.set_trace()
pdb.set_trace() drops the code into the debugger when execution
hits it:
#!/usr/bin/env python
import pdb
def test_debugger(some_int):
pdb.set_trace()
print "start some_int>>", some_int
return_int = 10 / some_int
print "end some_int>>", some_int
return return_int
if __name__ == "__main__":
test_debugger(0)
pdb.post_mortem(traceback)
pdb.post_mortem() performs postmortem debugging of the
specified traceback:
#!/usr/bin/env python
import pdb
def test_debugger(some_int):
print "start some_int>>", some_int
return_int = 10 / some_int
print "end some_int>>", some_int
return return_int
if __name__ == "__main__":
try:
test_debugger(0)
except:
import sys
tb = sys.exc_info()[2]
pdb.post_mortem(tb)
pdb.pm()
pdb.pm() performs postmortem debugging of the traceback
contained in sys.last_traceback:
#!/usr/bin/env python
import pdb
import sys
def test_debugger(some_int):
print "start some_int>>", some_int
return_int = 10 / some_int
print "end some_int>>", some_int
return return_int
def do_debugger(type, value, tb):
pdb.pm()
if __name__ == "__main__":
sys.excepthook = do_debugger
test_debugger(0)