Python Debugging with Decorators

I’ve written a little python function which I have found to be very helpful for debugging. It takes a function, and returns a function which is identical to the original except that it prints a message to the console with useful information every time the function is called or returns.

Here is the function:

# Number of times to indent output
# A list is used to force access by reference
__report_indent = [0]

## Decorator to print information about a function
#  call for use while debugging.
#  Prints function name, arguments, and call number
#  when the function is called. Prints this information
#  again along with the return value when the function
#  returns.
def report(fn):
    def wrap(*params,**kwargs):
        call = wrap.callcount = wrap.callcount + 1

        indent = ' ' * __report_indent[0]
        fc = "%s(%s)" % (fn.__name__, ', '.join(
            [a.__repr__() for a in params] +
            ["%s = %s" % (a, repr(b)) for a,b in kwargs.items()]
        ))

        print "%s%s called [#%s]"
            % (indent, fc, call)
        __report_indent[0] += 1
        ret = fn(*params,**kwargs)
        __report_indent[0] -= 1
        print "%s%s returned %s [#%s]"
            % (indent, fc, repr(ret), call)

        return ret
    wrap.callcount = 0
    return wrap

The function can be used as a decorator. For example, in this simple (and inefficient) recursive Fibonacci sequence function:

@report
def fibonacci(n):
    if n in [0,1]:
        return n
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)

The result:

>>> fibonacci(4)
fibonacci(4) called [#1]
 fibonacci(3) called [#2]
  fibonacci(2) called [#3]
   fibonacci(1) called [#4]
   fibonacci(1) returned 1 [#4]
   fibonacci(0) called [#5]
   fibonacci(0) returned 0 [#5]
  fibonacci(2) returned 1 [#3]
  fibonacci(1) called [#6]
  fibonacci(1) returned 1 [#6]
 fibonacci(3) returned 2 [#2]
 fibonacci(2) called [#7]
  fibonacci(1) called [#8]
  fibonacci(1) returned 1 [#8]
  fibonacci(0) called [#9]
  fibonacci(0) returned 0 [#9]
 fibonacci(2) returned 1 [#7]
fibonacci(4) returned 3 [#1]
3

The level of indent reflects the level of recursion, and the [#…] at the end of each line is the number of times the function has been called.

The level of indent is independent of the function being called, so it is helpful with mutual recursion as well. For example, when used with the functions even and odd from my earlier post on tail recursion, the result looks like this:

>>> even(5)
even(5) called [#1]
 odd(4) called [#1]
  even(3) called [#2]
   odd(2) called [#2]
    even(1) called [#3]
     odd(0) called [#3]
     odd(0) returned False [#3]
    even(1) returned False [#3]
   odd(2) returned False [#2]
  even(3) returned False [#2]
 odd(4) returned False [#1]
even(5) returned False [#1]
False

I find it useful to stick @report before the function I am having trouble with, and use comments to turn it on and off while I’m debugging that function. It can also be used at times other than function declaration, for example: report(base64.encodestring)(’test’).

Update (July 6, 2008): Fixed so that keyword arguments are printed as well.

Update (August 16, 2008): Changed .__repr__() to the more proper repr().

Posted on Jun 22nd, 2008 in Python

Comments

  1. Thats pretty amazing of a function. Mind if I borrow it? With proper credit of course in my files. :D

    Comment by Zeroth — June 26, 2008 @ 7:00 pm

  2. I also want to add, that your spam catcher shows up when I have noscript enabled. By rote reaction, I did exactly what it said, XD.

    Comment by Zeroth — June 26, 2008 @ 7:01 pm

  3. Zeroth, absolutely. I was thinking of using an open-source license but given that it is so short it seems to make more sense to just make it public domain. So consider it public domain; attribution is appreciated but not required.

    Re: the spam filter, I’m not sure exactly what you mean, but copying the number to the text field is how users without javascript prove they are human (users with javascript are assumed to be human, because so far bots do not generally run javascript).

    Comment by Paul Butler — June 26, 2008 @ 7:33 pm

Post a comment

For spam detection purposes, please copy the number 6824 to the field below: