Showing posts with label debugging. Show all posts
Showing posts with label debugging. Show all posts

Saturday, August 25, 2018

Automatically enter interactive mode after Python exception

By Vasudev Ram



Bug image attribution

Hi, readers,

Here's a Python command-line option that can facilitate debugging, when your program raises an exception:

It's the Python interpreter's -i option.

Here is a small Python program (ent-inter.py, for enter interactive mode) to show how the option can be used:
$ type ent-inter.py

from __future__ import print_function

print("before for loop")
for divisor in (1, 0, -1):
    print("1/{} = {}".format(divisor, 1/divisor))
print("after for loop")
If I run it in the normal way (without the -i option), I get:
$ python ent-inter.py
before for loop
1/1 = 1
Traceback (most recent call last):
  File "ent-inter.py", line 6, in 
    print("1/{} = {}".format(divisor, 1/divisor))
ZeroDivisionError: integer division or modulo by zero
The error message does tell us that there was a division by zero. But it doesn't tell us what exactly caused it.

Okay, in this particular case, from the stack trace (in particular, from the print statement), we can figure out that the variable divisor must have been zero. That was because I kept the code small and simple, though, for illustrative purposes.

But suppose that the cause of the error had been a more complex arithmetic expression, say with multiple division operations, or some other kind of statement (or sequence of statements). In that case, just the stack trace alone might not be enough for us to figure out the root cause of the error.

If we could immediately be launched into a Python interactive session with the current state (i.e. the variables) of the crashed program still available to inspect, that would likely help us to find the root cause. This is what the -i option helps with.

Let's see how:
$ python -i ent-inter.py
before for loop
1/1 = 1
Traceback (most recent call last):
  File "ent-inter.py", line 6, in 
    print("1/{} = {}".format(divisor, 1/divisor))
ZeroDivisionError: integer division or modulo by zero
>>> divisor
0
>>>
I ran the same program again, but this time with the -i option given.

After the program crashed due to the ZeroDivisionError, an interactive Python shell was automatically launched (as we can see from the Python prompt shown).

I typed "divisor" at the prompt, and the shell printed that variable's current value, 0.
From this (plus the stack trace), we can see that this value is the cause of the error. Then we can look one line above, in the program's code (at the for statement) and see that one of the items in the tuple is a zero.

Of course, we can run the program under the control of a command-line debugger (like pdb) or single-step through the code in an IDE, to find the error. But that will only work if the error occurs during one of those runs. If the error is intermittent, running the program multiple times using the debugger or an IDE, will be tedious and time-consuming.

Instead, with this -i option, we can run the program as many times as we want, and for the times when it works properly, we don't waste any time stepping through the code. But when it does give an error like the above one, we are launched into the interactive mode, with the state of the crashed program available for inspection, to help debug the issue.

Another advantage of this approach is that we may not need to replicate the problem, because we have it right there in front of us (due to use of the -i option), and also, replicating the exact conditions that cause a bug is not always easy.

Note: I ran this program with the -i option on both Python 2.7 and Python 3.7 [1] on Windows, and on Python 2.7 on Linux. Got the same results in all 3 cases, except that in Python 3, the error message is slightly different:

ZeroDivisionError: division by zero

[1] Running it with Python 3 (on Windows) can be done in the usual way, by changing your PATH to point to Python 3 (if it is not already set to that), or by using Py, the Python launcher for Windows, like this:
$ py -3 -i ent-inter.py
So, overall, Python's -i option is useful.

Here is an excerpt from the output of "python -h" (the python command's help option):
-i     : inspect interactively after running script;
The picture at the top of the post is of one of the first software bugs recorded.

Read that story here on Wikipedia: Software bug

Enjoy.

Interested in a standard or customized Python course? Contact me

- Vasudev Ram - Online Python training and consulting

Hit the ground running with my vi quickstart tutorial.

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



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


Monday, January 23, 2017

To log or not to log, that is the question

By Vasudev Ram


Hamlet image attribution

I was teaching some students about debugging print statements, so thought of doing a Google search for them.

Here is the search:

https://www.google.com/search?q=debugging+print+statements

Viewed a few of the search results. One, from the site softwareengineering.stackexchange.com, had an interesting discussion about the pros and cons of debugging print statements vs. logging vs. using a debugger:

Is printing to console/stdout a good debugging strategy?

And there are other interesting results of the search.

The title of this post is, of course, a word play on the famous quote:

To be, or not to be

from the play Hamlet by Shakespeare.

And the image at the top is of the actor Edwin Booth playing Hamlet.

- 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

Managed WordPress Hosting by FlyWheel



Wednesday, February 3, 2016

Using Python's trace module to understand the flow of programs (from many angles)

By Vasudev Ram


Some time back, I had written this post:

Python's trace module and chained decorators

in which I had briefly described use of the Python standard library's trace module to help us understand and debug Python programs. In that post I showed a small program with 3 chained decorators. The trace module was used to trace the execution of the decorators and the functions that they decorated.

The trace output in that post also showed the difference between function definition and function execution, both of which occur at run time in Python, since it is a dynamic language.

In this post, I'm going to use the trace module in a few other ways.

Here is a small program in which we will use the trace module.

(Note that I said "in which", not "on which", because I am going to invoke the trace module as a library from within this program, and tell it to start tracing from a specific function call, unlike in my previous post (linked above), in which I used the trace module as though it was a program itself, by using the "python -m" option, to trace my own program containing those decorators.)
# This is a program to show some basic usage of the trace module 
# from the Python standard library.
# Author: Vasudev Ram - 
# http://jugad2.blogspot.in/p/about-vasudev-ram.html
# Copyright 2016 Vasudev Ram

def fa():
    fb()

def fb():
    fc()

def fc():
    fd(5)

def fd(n):
    if n <= 1:
        return
    else:
        fd(n - 1)

import trace

tracer = trace.Trace(
    count=0, trace=0, countfuncs=0, countcallers=1, 
)

tracer.run('fa()')
r = tracer.results()
r.write_results(show_missing=True, coverdir=".")
In this program, I have a function fa calling fb which calls fc which calls fd. Function fd also calls itself recursively, with a termination condition so the recursion is not infinite.

Note that the program contains all the three main programming constructs: sequential execution of statements, conditional execution and iteration (via the recursive call in function fd).

From the Python documentation, the trace.Trace() constructor has the following (partial) signature:

class trace.Trace(count=1, trace=1, countfuncs=0, countcallers=0, ...)

where I have used ellipsis (...) to represent the remaining arguments, which I do not pass. (See the docs (linked above, near top of post) for the full signature and the meaning of all the arguments. The ones I use are explained below.)

As for what those arguments do, again, from the docs:

Create an object to trace execution of a single statement or expression. All parameters are optional. count enables counting of line numbers. trace enables line execution tracing. countfuncs enables listing of the functions called during the run. countcallers enables call relationship tracking.

I ran the program simple_prog.py 4 times. Each time I passed a different combination of argument values to trace.Trace() - with only one argument set to 1 each time, and all the others set to 0 in that run. And each time I redirected the resulting trace output to an output file, except for the first run, in which Python created the output in the file simple_prog.cover (since the program being traced is named simple_prog.py.

The lines below show the sets of arguments passed, and the corresponding output file names for the trace output. (I will show the contents of each output file later, below.)

arg set 1: count=1, trace=0, countfuncs=0, countcallers=0, output: simple_prog.cover

arg set 2: count=0, trace=1, countfuncs=0, countcallers=0, output: run_2.txt

arg set 3: count=0, trace=0, countfuncs=1, countcallers=0, output: run_3.txt

arg set 4: count=0, trace=0, countfuncs=0, countcallers=1, output: run_4.txt

Notice that in each of the arg sets, only one flag is set.

I ran the program this time with just:

python simple_prog.py

since the tracing is started from within the program, unlike in my previous post (linked above) in which I started the tracing like this:

python -m trace -t test_chained_decorators.py

Here is the output for the 1st run, simple_prog.cover:

# This is a program to show some basic usage of the trace module 
       # from the Python standard library.
       # Author: Vasudev Ram - 
       # http://jugad2.blogspot.in/p/about-vasudev-ram.html
       # Copyright 2016 Vasudev Ram
       
>>>>>> def fa():
    1:     fb()
       
>>>>>> def fb():
    1:     fc()
       
>>>>>> def fc():
    1:     fd(5)
       
>>>>>> def fd(n):
    5:     if n <= 1:
    1:         return
           else:
    4:         fd(n - 1)
       
>>>>>> import trace
       
>>>>>> tracer = trace.Trace(
>>>>>>     count=1, trace=0, countfuncs=0, countcallers=0, 
       )
       
>>>>>> tracer.run('fa()') # This line starts the tracing process.
>>>>>> r = tracer.results()
>>>>>> r.write_results(show_missing=True, coverdir=".")
You can see that the output includes the counts of the number of times lines of code are called. Notice that there are no line counts for the def lines, presumably because function definitions are only supposed to be done once (by definition, ha ha), so it would not make sense to show it, and might even be confusing.

Here is the output for the 2nd run, run_2.txt:

--- modulename: simple_prog, funcname: <module>
<string>(1):   --- modulename: simple_prog, funcname: fa
simple_prog.py(6):     fb()
 --- modulename: simple_prog, funcname: fb
simple_prog.py(9):     fc()
 --- modulename: simple_prog, funcname: fc
simple_prog.py(12):     fd(5)
 --- modulename: simple_prog, funcname: fd
simple_prog.py(15):     if n <= 1:
simple_prog.py(18):         fd(n - 1)
 --- modulename: simple_prog, funcname: fd
simple_prog.py(15):     if n <= 1:
simple_prog.py(18):         fd(n - 1)
 --- modulename: simple_prog, funcname: fd
simple_prog.py(15):     if n <= 1:
simple_prog.py(18):         fd(n - 1)
 --- modulename: simple_prog, funcname: fd
simple_prog.py(15):     if n <= 1:
simple_prog.py(18):         fd(n - 1)
 --- modulename: simple_prog, funcname: fd
simple_prog.py(15):     if n <= 1:
simple_prog.py(16):         return
 --- modulename: trace, funcname: _unsettrace
trace.py(80):         sys.settrace(None)
Since the trace flag is set, we get line execution tracing. And due to that, there are 5 entries for function fd, since there are 5 calls to it (of which 4 are recursive).

Here is the output for the 3rd run, run_3.txt:

functions called:
filename: <string>, modulename: <string>, funcname: <module>
filename: D:\Anaconda-2.1.0-64\lib\trace.py, modulename: trace, funcname: _unsettrace
filename: simple_prog.py, modulename: simple_prog, funcname: fa
filename: simple_prog.py, modulename: simple_prog, funcname: fb
filename: simple_prog.py, modulename: simple_prog, funcname: fc
filename: simple_prog.py, modulename: simple_prog, funcname: fd
Since the countfuncs flag is set, it shows the functions called during the run of the program.

Here is the output for the 4th run, run_4.txt:

calling relationships:

*** <string> ***
  --> simple_prog.py
    <string>.<module> -> simple_prog.fa

*** D:\Anaconda-2.1.0-64\lib\trace.py ***
  --> <string>
    trace.Trace.runctx -> <string>.<module>
    trace.Trace.runctx -> trace._unsettrace

*** simple_prog.py ***
    simple_prog.fa -> simple_prog.fb
    simple_prog.fb -> simple_prog.fc
    simple_prog.fc -> simple_prog.fd
    simple_prog.fd -> simple_prog.fd
Since the countcallers flag is set, it shows the function call tree during the run of the program, i.e. what function called what other function(s). The last line shows the recursive call to fd.

So we can see, overall, that those four flags to trace.Trace(), allowed us to get insight into the behaviour of the program, from various angles or perspectives. This makes the trace module a useful tool for debugging and for understanding code that we have to work on.

- Enjoy.

- Vasudev Ram - Online Python training and programming

Signup to hear about new products and services I create.

Posts about Python  Posts about xtopdf

My ActiveState recipes

Monday, March 30, 2015

dunderdoc, a simple Python introspection utility

By Vasudev Ram



While browsing Python documentation, I came up with the idea of this small Python introspection utility.

It prints the __doc__ attribute (the docstring) of each item in a list of names given as argument to it. So I called the function 'dunderdoc()' - because it is an informal convention in the Python community to call attributes such as __name__, that begin and end with a double underscore, dunder-name, and so on.

Here is the code for dunderdoc.py:

"""
dunderdoc.py
A Python function to print the .__doc__ attribute (i.e. the docstring) 
of each item in a list of names given as the argument.
The function is called dunderdoc because it is an informal convention 
in the Python community to call attributes such as __name__, that begin 
and end with a double underscore, dunder-name, and so on.
Author: Vasudev Ram - http://www.dancingbison.com
Copyright 2015 Vasudev Ram
"""

def dunderdoc(names):
    for name in names:
        print '-' * 72
        print name + '.__doc__:'
        print eval(name).__doc__
    print '-' * 72

# Call dunderdoc() on some basic objects:

a = 1 # an integer
b = 'abc' # a string
c = False # a boolean
d = () # a tuple
e = [] # a list
f = {} # a dict
g = set() # a set

dunderdoc(('a', 'b', 'c', 'd', 'e', 'f', 'g'))

# Call dunderdoc() on some user-defined objects:

class Foo(object):
    """
    A class that implements Foo instances.
    """

def bar(args):
    """
    A function that implements bar functionality.
    """

dunderdoc(['Foo', 'bar'])

And here is the output of running dunderdoc.py with the example calls to the dunderdoc() function:
------------------------------------------------------------------------
a.__doc__:
int(x=0) -> int or long
int(x, base=10) -> int or long

Convert a number or string to an integer, or return 0 if no arguments
are given.  If x is floating point, the conversion truncates towards zero.
If x is outside the integer range, the function returns a long instead.

If x is not a number or if base is given, then x must be a string or
Unicode object representing an integer literal in the given base.  The
literal can be preceded by '+' or '-' and be surrounded by whitespace.
The base defaults to 10.  Valid bases are 0 and 2-36.  Base 0 means to
interpret the base from the string as an integer literal.
>>> int('0b100', base=0)
4
------------------------------------------------------------------------
b.__doc__:
str(object='') -> string

Return a nice string representation of the object.
If the argument is a string, the return value is the same object.
------------------------------------------------------------------------
c.__doc__:
bool(x) -> bool

Returns True when the argument x is true, False otherwise.
The builtins True and False are the only two instances of the class bool.
The class bool is a subclass of the class int, and cannot be subclassed.
------------------------------------------------------------------------
d.__doc__:
tuple() -> empty tuple
tuple(iterable) -> tuple initialized from iterable's items

If the argument is a tuple, the return value is the same object.
------------------------------------------------------------------------
e.__doc__:
list() -> new empty list
list(iterable) -> new list initialized from iterable's items
------------------------------------------------------------------------
f.__doc__:
dict() -> new empty dictionary
dict(mapping) -> new dictionary initialized from a mapping object's
    (key, value) pairs
dict(iterable) -> new dictionary initialized as if via:
    d = {}
    for k, v in iterable:
        d[k] = v
dict(**kwargs) -> new dictionary initialized with the name=value pairs
    in the keyword argument list.  For example:  dict(one=1, two=2)
------------------------------------------------------------------------
g.__doc__:
set() -> new empty set object
set(iterable) -> new set object

Build an unordered collection of unique elements.
------------------------------------------------------------------------
------------------------------------------------------------------------
Foo.__doc__:

    A class that implements Foo instances.
    
------------------------------------------------------------------------
bar.__doc__:

    A function that implements bar functionality.
    
------------------------------------------------------------------------

The image at the top of the post is of Auguste Rodin's Le Penseur (The Thinker).

- Enjoy.

- Vasudev Ram - Online Python training and programming

Dancing Bison Enterprises

Signup to hear about new products that I create.

Posts about Python  Posts about xtopdf

Contact Page

Thursday, May 29, 2014

Another simple Python debugging function

By Vasudev Ram


I had written this little Python function for debugging, a while ago.

Then I forgot about it. Was reminded of it today by seeing by this Reddit Python post:

debug_print - A tiny package(file) for printf style debugging.

That author's debug_print function uses the Python eval function, which my function also does. That is what reminded me of my function. Here it is:
# eval_debug.py
# A program to implement and test ed(), a debugging function.
# Author: Vasudev Ram - http://www.dancingbison.com

def ed(item):
    print item + ": |" + repr(eval(item)) + "|"

# Integer
intt = 42
ed('intt')

# Floating point
flot = 1.0
ed('flot')

# String
strng = "foo"
ed('strng')

# Boolean
boolan = False
ed('boolan')

# Tuple
tupl = (1, "two", True)
ed('tupl')

# List
lis = [ 3, 4, 5 ]
ed('lis')

# Dict
dic = { "a": "apple", "b": "banana" }
ed('dic')

# Set
sett = set({ 2, 3, 5, 7, 9 })
ed('sett')

# Function
def funk():
    pass
ed('funk')

# Generator
def g():
    yield 1
gen = g()
ed('gen')

# Class
class klas:
    pass
ed('klas')

# EOF
The purpose of the ed() debugging function is simple: to be able to display a variable and its value without having to pass the name twice in the print statement, once with the variable name in quotes (to print its name) and once with the variable name not in quotes (to print its value). The function currently only works for some cases, though (*).

Here is the output of running python eval_debug.py :
$ python eval_debug.py
intt: |42|
flot: |1.0|
strng: |'foo'|
boolan: |False|
tupl: |(1, 'two', True)|
lis: |[3, 4, 5]|
dic: |{'a': 'apple', 'b': 'banana'}|
sett: |set([9, 2, 3, 5, 7])|
funk: ||
gen: ||
klas: ||

(*) So the debugging function has a bug :-)


Also check out my earlier post on the same topic, Python debugging:

A simple Python debugging function

And see other posts about debugging on my blog.


- Vasudev Ram - Dancing Bison Enterprises

Contact Page

Saturday, November 30, 2013

Using inspect.getargvalues to debug Python programs

By Vasudev Ram


I was looking through the Python docs recently, for ideas for any other useful debugging techniques, some time after I wrote this post about a simple Python debugging function.

Though I initially tried something else, I later came up with this technique, which may be helpful to debug Python programs. It uses the getargvalues function of Python's inspect module.

Here is a test program showing the use of inspect.getargvalues(); note that you have to pass the return value of inspect.currentframe() to inspect.getargvalues():
# test_getargvalues.py
# Author: Vasudev Ram - http://dancingbison.com

from debug1 import debug1
import inspect

def foo(arg1, arg2=None):
    print "In foo()"
    a = 1
    b = "2"
    c = True
    d = [ 3, "4" ]
    e = { 5: "five", "six": 6 }
    argvalues = inspect.getargvalues(inspect.currentframe())
    debug1("argvalues.args", argvalues.args)
    debug1("argvalues.varargs", argvalues.varargs)
    debug1("argvalues.keywords", argvalues.keywords)
    debug1("argvalues.locals", argvalues.locals)
    debug1("locals()", locals())

def bar(arg1, arg2, *args, **kwds):
    print "In bar()"
    argvalues = inspect.getargvalues(inspect.currentframe())
    debug1("argvalues.args", argvalues.args)
    debug1("argvalues.varargs", argvalues.varargs)
    debug1("argvalues.keywords", argvalues.keywords)
    debug1("argvalues.locals", argvalues.locals)
    debug1("locals()", locals())

def main():
    foo(1, 2)
    bar(1, 2, 3, 4, five=5, six=6)

main()
Run the program with:

python test_getargvalues.py

Here is its output:
In foo()
argvalues.args : ['arg1', 'arg2']
argvalues.varargs : None
argvalues.keywords : None
argvalues.locals : {'a': 1, 'c': True, 'b': '2', 'e': {'six': 6, 5: 'five'}, 'd': [3, '4'], 'arg1': 1, 'arg2': 2}
locals() : {'a': 1, 'c': True, 'b': '2', 'e': {'six': 6, 5: 'five'}, 'd': [3, '4'], 'arg1': 1, 'arg2': 2, 'argvalues': ArgInfo(args=['arg1', 'arg2'], varargs=None, keywords=None, locals={...})}
In bar()
argvalues.args : ['arg1', 'arg2']
argvalues.varargs : 'args'
argvalues.keywords : 'kwds'
argvalues.locals : {'arg1': 1, 'arg2': 2, 'args': (3, 4), 'kwds': {'six': 6, 'five': 5}}
locals() : {'arg1': 1, 'arg2': 2, 'args': (3, 4), 'kwds': {'six': 6, 'five': 5}, 'argvalues': ArgInfo(args=['arg1', 'arg2'], varargs='args', keywords='kwds', locals={...})}
Note that for comparison, I also printed the value of the Python built-in locals(), and found that the output of locals() is almost the same as, but a subset, of the output of getargvalues() - at least when the function which you are debugging has varargs and keyword arguments.

Read other posts about Python on jugad2.
- Vasudev Ram - Dancing Bison Enterprises
Contact Page


O'Reilly 50% Ebook Deal of the Day


Friday, October 25, 2013

A simple Python debugging function


By Vasudev Ram


[ Update: I do know about Python's logging module but felt it was overkill for my current needs, and also wanted more flexibility. ]

[ Update 2: Made some minor improvement to the code for better output. You can now print a debug message without any associated values. ]

I just whipped up this simple debugging function to help me debug my Python programs:
# vr_debug.py

# Debug utility functions by VR.
# Author: Vasudev Ram - http://www.dancingbison.com
# Description: Simple utility functions for debugging Python programs.

import sys
import os

def vr_debug(message, *values):
    vr_debugging = os.getenv("VR_DEBUG")
    if vr_debugging is None:
        # Debugging is off, do nothing.
        return
    if len(values) == 0:
        print message,
    else:
        print message, ":", 
    #print "len(values):", len(values)
    for value in values:
        #print repr(value), " ",
        print repr(value),
    print

def main():
    # Test the vr_debug function with some calls.
    vr_debug('z') # Debug message without any associated values.
    # Debug messages with values:
    vr_debug('a', 1)
    vr_debug('b', 1, "hi")
    vr_debug('c', 1, "hi", True)
    vr_debug('d', 1, "hi", True, [2, 3])
    vr_debug('d', 1, "hi", True, [2, 3], {'a': 'apple', 'b': 'banana'})

if __name__ == '__main__':
    main()

# EOF

To use the vr_debug function (as shown in the above program), you have to set an environment variable VR_DEBUG to some non-null value, say 1:
C:\>set VR_DEBUG=1
Then you can run the program to test the debug function, with:
C:\>python vr_debug.py
Here is its output:
z
a : 1
b : 1 'hi'
c : 1 'hi' True
d : 1 'hi' True [2, 3]
d : 1 'hi' True [2, 3] {'a': 'apple', 'b': 'banana'}
To turn off debugging, just set VR_DEBUG to a null value:
C:\>set VR_DEBUG=

Note: The above environment variable settings were for Windows. For Linux/UNIX, using bash, you would do this:
$ export VR_DEBUG=1
to set the environment variable, but the unset command to unset it:
$ unset VR_DEBUG

The above program included both the vr_debug function and a few tests of it. To use the vr_debug function in your own code, just copy the file vr_debug.py to somewhere on your PYTHONPATH and then import it into any Python files in which you want to use it:
from vr_debug import vr_debug
Now you can call it just as in the above program.

This is just a simple debug function, improvements are possible, such as passing an output destination argument, with a default of sys.stderr (so that output is un-buffered, unlike with the print command), and also so that a different destination can be given, such as a file opened for writing. To do that, you would have to change all the print statements to something like:
out_fil.write(...)
, and add
out_fil=sys.stderr
as another argument to the vr_debug function.

I've written a few different variations of such debugging functions over the years, in Python as well as other languages, but have somehow never seemed to arrive at a solution that satisfies me.

On the other hand, there is something to be said for KISS ...

For another Python debugging technique, see my earlier post:

Using sys._current_frames() and the Python traceback module for debugging

- Vasudev Ram - Dancing Bison Enterprises

Contact me

Sunday, November 18, 2012