Print

Interactive Debugging in Python

by Jeremy Jones
09/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
By Alex Martelli, Anna Martelli Ravenscroft, David Ascher

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)

Pages: 1, 2, 3, 4, 5, 6, 7

Next Pagearrow