Lovely Systems Lovely Systems Blog entries Conditional Debugging In Python

Conditional Debugging In Python


Conditional Debugging vs. Conditional Break Points

We at Lovely are Python engineers (amongst many other things). We prefer to do TDD, so we set up Python doctests for our implementations.

Even though we like to keep things simple, there are times when things get a bit tricky. In such situations one might want to look a little bit deeper into what's actually going on. Luckily, there are ways in Python to do exactly that. PDB (2)/(3) to the rescue!

PDB (The Python Debugger) is great. It's interactive and offers conditional break points (see PyMOTW to get a short introduction). Conditional break points allow to skip e.g. several iterations of a for-loop and stop when a certain condition is met.

However, there are situations where I do not want to start the application, set a break point and wait until things go wrong. In a test suite, there might be several test cases which do similar things and all goes well. Except for that one other case. In situations like described, I've developed following pattern.

A little helper class (called Dbg) holds the condition if the PDB's trace should be set. In my test case, I'll set the condition for when the PDB's trace should be set. See the following example for more details.

Suppose the following function:

"""
trickyFunctionHandler.py
"""
import trickyDependency


class Dbg(object):
    active = False
DEBUGGER = Dbg()


def someTrickyFunction(*args, **kwargs):
    if DEBUGGER.active:
        import pdb; pdb.set_trace()
    result = trickyDependency.doSupposedlyEasyStuff(*args, **kwargs)
    return result == 'ok'

Note, that the DEBUGGER (the previously described Dbg class) is already in place and ready to be used.

Let's assume, that the trickyDependency does not behave as expected. The doctest file may look as follows:

Test setup of 'trickyFunctionHandler.rst'

    >>> from trickyFunctionHandler import someTrickyFunction

Several cases which call ``someTrickyFunction``.

    >>> for i in range(10):
    ...     someTrickyFunction(i)

My special case where I'd like to step in and take a closer look::

    >>> from trickyFunctionHandler import DEBUGGER
    >>> DEBUGGER.active = True

    >>> someTrickyFunction('something weird')

    >>> DEBUGGER.active = False

    >>> # more test cases

Now, all test cases pass the debugger except for the one I'd like to inspect.

This could also be done in a simpler way by only defining a boolean flag e.g. DEBUGGER = False instead of using a class. However, using a class has some advantages over a simple boolean flag. One could, for example, define additional helper methods or pass on useful information while debugging (even though I almost never use it that way).

That's all for now. I hope this is of help to someone out there. Happy coding! :)

Goodies for VIM users:

I've used this pattern for quite some time now. So if you're a VIM user you can put the following into your .vimrc.

Those are my shortcuts to:

  • F4 writes the Dbg class and initialise DEBUGGER with active = False.
  • F5 write the "debug condition".
  • F6 imports the DEBUGGER and sets active = True and pre-selectes the ... which needs to be replace from where the DEBUGGER is actually imported.
if has('autocmd')
    autocmd BufRead *.py nmap <F4> oclass Dbg(object):<esc>oactive = False<esc>oDEBUGGER = Dbg()<esc>V<
    autocmd BufRead *.py nmap <F5> oif DEBUGGER.active:<esc>oimport pdb; pdb.set_trace()<esc>
    autocmd BufRead *.{txt,rst,py} nmap <F6> o>>> from ... import DEBUGGER<esc>o>>> DEBUGGER.active = True<esc>k0f.ve
endif


Written by Lukas Ender

Posted on June 24, 2015

Lovely Systems