Friday, June 1, 2018

Improved simple Python debugging function

By Vasudev Ram

[ I rewrote parts of this post, which was originally published two days ago on my blog (but intentionally not to the Planet Python, earlier, because I did not set the python label that makes that happen), for more clarity and to give a standalone example of the use of the use of the debug1 function. ]

I had blogged earlier about this Python debugging function, vr_debug, that I created a while ago:

A simple Python debugging function

Some time later I created an improved version of it, that does not need the user to set an environment variable to turn the debugging on or off.

Here is the code for the new function, now called debug1, in module debug1:
# debug1.py

from __future__ import print_function

# A simple debugging function for Python programs.
# How to use it:
# If the -O option is not given on the Python command line (the more common case 
# during development), the in-built special variable __debug__ is defined as True, 
# and the debug1 function displays debugging messages, i.e. it prints the message 
# and all other (optional) values passed to it.
# If the -O option is given, the variable __debug__ is defined as False, 
# and the debug1 function does nothing is defined as a no-op, so does nothing.

import os

if __debug__:
    def debug1(message, *values):
        if len(values) == 0:
            print(message)
        else:
            print("{}:".format(message), end=" ")
            print(" ".join([repr(value) for value in values]))
else:
    def debug1(message, *values):
        # Do nothing.
        pass

def main():
    # Test the debug1 function with some calls.
    debug1('message only')
    debug1('message with int', 1)
    debug1('message with int, float', 1, 2.3)
    debug1('message with long, string', 4L, "hi")
    debug1('message with boolean, tuple, set', True, (1, 2), { 3, 4} )
    debug1('message with string, boolean, list', "hi", True, [2, 3])
    debug1('message with complex, dict', 1 + 2j, {'a': 'apple', 'b': 'banana'})
    class Foo: pass
    foo = Foo()
    debug1('message with object', foo)
    debug1('message with class', Foo)
    debug1('message with xrange', xrange(3))
    debug1('message with listiterator', iter(range(4)))

if __name__ == '__main__':
    main()

To use it in the normal way, import the debug1() function from the debug1 module into a Python file where you want to use it.
Then just call the function in your code at the places where you want to print some message, with or without the value of one or more variables. Here is an example:


# In your program, say factorial.py
from debug1 import debug1

def factorial(n):
    # This line prints the message and the value of n when debug1 is enabled.
    debug1("entered factorial, n", n)
    if n < 0:
        raise Exception("Factorial argument must be integer, 0 or greater.")
    if n == 0:
        return 1
    p = 1
    for i in range(1, n + 1):
        p *= i
        # This line prints the message and the changing values 
        # of i and p when debug1 is enabled.
        debug1("in for loop, i, p", i, p)
    return p

print "i\tfactorial(i)"
for i in range(6):
    print "{}\t{}".format(i, factorial(i))
Then to run factorial.py with debugging on, no specific enabling step is needed (unlike with the earlier version, vr_debug.py where you had to set the environment variable VR_DEBUG to 1 or some other non-null value). Just run your program as usual and debugging output will be shown:
$ python factorial.py
i       factorial(i)
0       1
in for loop, i, p: 1 1
1       1
in for loop, i, p: 1 1
in for loop, i, p: 2 2
2       2
in for loop, i, p: 1 1
in for loop, i, p: 2 2
in for loop, i, p: 3 6
3       6
in for loop, i, p: 1 1
in for loop, i, p: 2 2
in for loop, i, p: 3 6
in for loop, i, p: 4 24
4       24
in for loop, i, p: 1 1
in for loop, i, p: 2 2
in for loop, i, p: 3 6
in for loop, i, p: 4 24
in for loop, i, p: 5 120
5       120
Once you have debugged and fixed any bugs in your program, with the help of the debugging output, you can easily turn off debugging messages like this, by adding the -O option to the python command line, to get only the normal program output:
$ python -O factorial.py
i       factorial(i)
0       1
1       1
2       2
3       6
4       24
5       120
The debug1 module internally checks the value of the built-in Python variable __debug__, and conditionally defines the function debug1() as either the real function, or a no-op, based on __debug__'s value at runtime.

The __debug__ variable is normally set by the Python interpreter to True, unless you pass python the -O option, which sets it to False.

Know of any different or better debugging functions? Feel free to mention them in the comments. Like I said in the previous post about the first version of this debug function (linked above), I've never been quite satisfied with the various attempts I've made to write debugging functions of this kind.

Of course, Python IDEs like Wing IDE or PyCharm can be used, which have features like stepping through (or over, in the case of functions) the code, setting breakpoints and watches, etc., but sometimes the good old debugging print statement technique is more suitable, particularly when there are many iterations of a loop, in which case the breakpoint / watch method becomes tedious, unless conditional breakpoints or suchlike are supported.

There are also scenarios where IDE debugging does not work well or is not supported, like in the case of web development. Although some IDEs have made attempts in this direction, sometimes it is only available in a paid or higher version.


Interested in learning Python programming by email? Contact me for the course details (use the Gmail id at that preceding link).
Enjoy.

- Vasudev Ram - Online Python training and consulting

Get updates (via Gumroad) on my forthcoming apps and content.

Jump to posts: Python * DLang * xtopdf

Subscribe to my blog by email

My ActiveState Code recipes

Follow me on: LinkedIn * Twitter

Are you a blogger with some traffic? Get Convertkit:

Email marketing for professional bloggers


2 comments:

Vasudev Ram said...

Readers seeing this post via Planet Python (or other aggregators) may see this in the code:

if n 0:

instead of this:

if n < 0:

because I forgot to escape the less than sign. It shows okay without the escaping in Blogger but may not in some aggregators, and does not show okay in Planet Python. Sorry about that oversight.

Vasudev Ram said...

Damn, another typo :)

This:

>a standalone example of the use of the use of the debug1 function.

should read as:

a standalone example of the use of the debug1 function.