Python Debugging Techniques

This article covers several techniques for debugging Python programs. The applicability of these techniques ranges from simple scripts to complex applications. The topics that are covered include launching an interactive console from within your program, using the Python debugger, and implementing robust logging. Various tips are included along the way to help you debug and fix problems quickly and efficiently.

Launching an interactive console with code.interact()

The Python interactive console is an awesome tool for experimenting with Python code. It provides a read-eval-print loop that allows you to experiment with Python code easily and quickly, without having to write and run a complete program. Wouldn't be convenient if you could use the same technique to debug an existing program? Fortunately, this is already possible thanks to the code module. This module has a function called interact() that stops execution and gives you an interactive console to examine the current state of your program. To use this function, simply embed the following at the line were you want the console to start:

import code; code.interact(local=locals())

The resulting console inherits the local scope of the line on which code.interact() is called. This enables you to check the current state of your program to understand its behavior and make any necessary corrections.

To exit the interactive console and continue with the execution of your program, press Ctrl+D in Unix/Linux systems, or Ctrl+Z in Windows. Alternatively, you can type exit() in the console and hit enter to exit the console and abort your program.

Using the Python Debugger

When you need to examine the execution flow of your program, an interactive console is not going to be enough. For situations like this, you can use the Python debugger. It provides a full-fledged debugging environment that supports breakpoints, stepping through source code, stack inspection and much more. This debugger comes with the standard python distribution as a module named pdb.

To learn how to use pdb, let's write a simple program that calculates the first 10 Fibonacci numbers:

def main():
  low, high = 0, 1
  for i in xrange(10):
    print high
    low, high = high, low + high
 
if __name__ == '__main__':
  main()

Assuming that the file name is fib.py, we can run the program in pdb using the following command:

$ python -m pdb fib.py

The command results in the following output:

> fib.py(1)<module>()
-> def main():
(Pdb) 

The first line of output contains the current module name, line number and function name (function name currently appears as <module>() because we are still at module level). The second line of output contains the current line of source code that is being executed. pdb also provides an interactive console to debug the program. This console is different from the familiar Python console. You can see its commands by typing help and hitting enter. Also, typing help <command> gives you help on the command you provided. Let's learn about these commands.

Stepping through program execution

The list command prints a few lines of context around the current line. Let's give it a try:

(Pdb) list
  1  -> def main():
  2       low, high = 0, 1
  3       for i in xrange(10):
  4         print high
  5         low, high = high, low + high
  6  
  7     if __name__ == '__main__':
  8       main()
[EOF]

Next, let's step through our program. The next command executes the current line and moves next.

(Pdb) next
> fib.py(7)<module>()
-> if __name__ == '__main__':

At this point, the main() function has been defined, but not called. This is why the execution has jumped to the if condition on line 7. Let's call next again:

(Pdb) n
> fib.py(8)<module>()
-> main()

We are now about to call the main() function. Running next at this point will call main() and move to the next line. Since we are at the end of the file, this means that the program will finish executing. But we don't want this; we want to step into the main() function. To do this, we use the step command:

(Pdb) step
--Call--
> fib.py(1)main()
-> def main():

From here, you can continue calling next to step through the main(). If at some point you want to examine the value of a variable, you can use the pp command (short for pretty-print):

(Pdb) pp high
2

If you are done with examining the main() function, you can either use the continue command, which exits the debugging console and continues the execution of the program, or use the return, which continues the execution until the current function returns. Alternatively, you can stop the execution altogether and abort by using the exit command.

Setting breakpoints

Next, we will learn about breakpoints. More often than not, you want to invoke the debugger at a particular function or line number, rather than step through the execution of the whole program. To do so, you can set a breakpoint and continue the execution of the program. When the breakpoint is reached, the debugger is invoked.

To set a breakpoint, use the break command. It takes a file name and line number or function name. To break at line 4 in fib.py, use:

(Pdb) break fib.py:4

To break when the main() function is called, use:

(Pdb) break fib.main

Furthermore, you can attach a condition to the breakpoint. Execution breaks only if this condition is True. For example, to break at line 4 in fib.py when high is greater than 10, use:

(Pdb) break fib.py:4, high > 10

The best pdb tip

Now it's time for my favorite feature in pdb. It you put the following snippet somewhere in your program and run it normally, execution will stop and a debugging session will start when this line is reached:

import pdb; pdb.set_trace()

This approach is very convenient because it does not require launching your program in a special way or remembering to set breakpoints. You simply add the line above and start the program normally, and the debugger will be invoked exactly where you want. In practice, I think you will use this snippet to start pdb most of the time.

Summary of pdb commands and short forms

Finally, pdb commands have short forms. The following table summarizes the commands presented in this section, and their short forms:

Command Short form Description
break b Set a breakpoint.
continue c Continue with program execution.
exit q Abort the program.
help h Print list of commands or help for a given command.
list l Show source code around current line.
return r Continue execution until the current function returns.

Logging

A primitive way of debugging programs is to embed print statements through out the code to track execution flow and state. However, this approach can quickly become unmaintainable for a number of reasons:

  • Normal program output is mixed with debugging output. This makes it difficult to distinguish between the two.
  • There is no easy way to disable debugging output, or redirect it to a file.
  • When done with debugging, it may be difficult to track down and remove all print statements that are scattered over the code.

Python provides an alternative to debug print statements that doesn't suffer from the shortcomings above. This alternative comes in the form of a module called logging, and it is very powerful and easy to use.

Let's start with a simple example. The following snippet imports the logging module and sets the logging level to debug:

import logging
 
logging.basicConfig(level=logging.DEBUG)

The call to logging.basicConfig() should be done once when your program starts. Now, whenever you want to print a debug message, call logging.debug():

logging.debug('This is a debug message.')

This will send the following string to stderr:

DEBUG:root:This is a debugging message.

DEBUG indicates that this is a debug message. root indicates that this is the root logger, as it is possible to have multiple loggers (don't worry about this for now).

Now we have a better logging system that can be globally switched on and off. To turn off debug messages, simply omit the level argument when calling logging.basicConfig():

logging.basicConfig()

Logging to a file and adding date/time

To take full advantage of the logging module, let's have a look at some of the options that can be provided to logging.basicConfig():

Argument Description
filename Send log messages to a file.
filemode The mode to open the file in (defaults to 'a').
format The format of log messages.
dateformat date/time format in log messages.
level Level of messages to be printed (more on this later).

For example, to configure the logging module to send debug messages to a file called debug.log, use:

logging.basicConfig(level=logging.DEBUG, filename='debug.log')

Log messages will be appended to debug.log if the file already exists. This means that your log messages will be kept even if you run your program multiple times.

To add date/time to your log messages, use:

logging.basicConfig(level=logging.DEBUG, filename='debug.log',
                    format='%(asctime)s %(levelname)s: %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S')

This will result in log messages like the following:

2009-08-30 23:30:49 DEBUG: This is a debug message.

Logging levels

The logging supports multiple levels of log messages in addition to DEBUG. Here is the full list:

Level Function
logging.CRITICAL logging.critical()
logging.ERROR logging.error()
logging.WARNING logging.warning()
logging.INFO logging.info()
logging.DEBUG logging.debug()

Setting the logging level to a value enables log messages for this level and all levels above it. So if you set the level to logging.WARNING, you will get WARNING, ERROR and CRITICAL messages. This allows you to have different levels of log verbosity.

Convenient template for logging

Before I conclude this section, I will provide a simple template for enabling logging functionality in your programs. This template uses command-line flags to change the level logging, which is more convenient that modifying source code.

import logging
import optparse
 
LOGGING_LEVELS = {'critical': logging.CRITICAL,
                  'error': logging.ERROR,
                  'warning': logging.WARNING,
                  'info': logging.INFO,
                  'debug': logging.DEBUG}
 
def main():
  parser = optparse.OptionParser()
  parser.add_option('-l', '--logging-level', help='Logging level')
  parser.add_option('-f', '--logging-file', help='Logging file name')
  (options, args) = parser.parse_args()
  logging_level = LOGGING_LEVELS.get(options.logging_level, logging.NOTSET)
  logging.basicConfig(level=logging_level, filename=options.logging_file,
                      format='%(asctime)s %(levelname)s: %(message)s',
                      datefmt='%Y-%m-%d %H:%M:%S')
 
  # Your program goes here.
  # You can access command-line arguments using the args variable.
 
if __name__ == '__main__':
  main()

By default, the logging module prints critical, error and warning messages. To change this so that all levels are printed, use:

$ ./your-program.py --logging=debug

To send log messages to a file called debug.log, use:

$ ./your-program.py --logging-level=debug --logging-file=debug.log

 

Tags:
Submitted by Ayman on Mon, 2009/08/31 - 8:54am

Cody Soyland (not verified) | Another great debugging tool | Mon, 2009/08/31 - 1:18pm

Another great debugging tool is IPDB, the interactive python debugger. It combines the best of PDB with the nice colored output and tab completion we are used to from iPython. It is invoked the same way as PDB: import ipdb; ipdb.set_trace()

spditner (not verified) | logging from inside modules | Mon, 2009/08/31 - 1:20pm

One thing that I've noticed about logging from modules, particularly in django, is that if you don't ask 'logging' for a logger, you won't be able to determine where exactly your messages are coming from.

For instance, if you used the following format, it would frequently report your messages as coming from the logging module, as opposed to your code:

format='%(asctime)s %(levelname)s: %(message)s' %(pathname)s:%(lineno)d'

To prevent this, I fetch a logging channel from within the module I'm logging from:

import logging
log = logging.getLogger('')  # generic channel ''
log.debug('blah')            # path and line number reported correctly
...

spditner (not verified) | Launching a prettier interactive console with full colour | Mon, 2009/08/31 - 1:35pm

If you have ipython installed, you can use it's interactive interpreter from within your code as well. It gives you syntax highlighting and tab completing of things in the namespace for the object being inspected. Place this at the line in question:

from IPython.Shell import IPShellEmbed; IPShellEmbed()()

Anonymous (not verified) | Another Python Debugger | Mon, 2009/08/31 - 4:48pm

Give Winpdb a try as well.

http://sourceforge.net/projects/winpdb/

geo (not verified) | Go with ipdb | Mon, 2009/08/31 - 6:26pm

I second the ipdb module. I think it's much easier to debug code who's syntax is highlighted.

Ayman | Thanks everyone for your | Mon, 2009/08/31 - 6:44pm

Thanks everyone for your contributions. I appreciate your comments!

Anonymous (not verified) | Another nice feature | Mon, 2009/08/31 - 9:07pm

In CPython, at least on Windows, you can run the interpreter using the -i command-line parameter, e.g:
    python -i myscript.py
It gives you an interactive console after the program is finished. This is handy for inspecting the state of the program, and it also works in case of an unhandled exception.

Ayman | Thanks for your tip. To | Mon, 2009/08/31 - 9:56pm

Thanks for your tip. To confirm, this works on all platforms.

Aaron (not verified) | Thanks for sharing your tip. | Tue, 2009/09/01 - 6:42pm

Thanks for sharing your tip. I'll certainly pass it on to my development team. (even though the majority of our applications are PHP/MySQL and AJAX based.

Regards,

Aaron

Eli (not verified) | Thanks for the interesting post | Fri, 2009/09/04 - 4:08am

Nice summary, Ayman. It was especially useful to see the parts on dropping into an interactive console or PDB from inside the code using special statements.

Jon (not verified) | Solid post | Tue, 2009/09/15 - 2:39pm

I had heard of Python debugger, but a friend who tried to explain how to use it to me was just a really bad teacher. This post was more than helpful and I will be using Pythin from now on. What a great tool.

macdet (not verified) | thx a lot | Tue, 2009/12/01 - 9:30am

this was very helpful. I try to learn more and write my learning-steps in my python-wiki http://wiki.mobbing-gegner.de/Python

its a hard way for an old man :(

derar (not verified) | Eclipse + Pydev | Sun, 2009/12/27 - 4:17am

I use Eclipse + Pydev for my Python scripting; Pydev provides very powerful debugger with many features like conditional breakpoints and remote debugger with very neat GUI that shows all the properties of the functions, variables, .. etc.
I was looking for a module like logging !! I didn't know that it exists ;) thank you for sharing Ayman :)

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <strike> <code> <ul> <ol> <li> <blockquote> <sup> <sub> <b> <i> <u>
  • Lines and paragraphs break automatically.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.

More information about formatting options

About

Ayman Hourieh

Computer Science graduate, Open Source enthusiast and Software engineer (Site reliability) at Google.

I'm 26 years old, and live in Dublin, Ireland.

This is my personal blog. The views expressed on these pages are mine alone and not those of my employer.

More

Feeds

RSS Feed Twitter

Books

Django 1.0 Website Development

Django 1.0 Website Development
Build powerful web applications, quickly and cleanly, with the Django application framework.

The second edition of my Django book. Published by Packt Publishing in March 2009.

Learning Website Development with Django

Learning Website Development with Django
A beginner's tutorial to building web applications, quickly and cleanly, with the Django application framework.

My first book. Published by Packt Publishing in April 2008.