Interactive Debugging in Python
Pages: 1, 2, 3, 4, 5, 6, 7
The only reason the interpreter would have returned between lines 14 and 18
is that it hit an exception. Because the debugger has a couple of nifty
postmortem functions, I modified my script to use one, fired it up to see where
it stopped, and started debugging from there. Here is the modified source file; pay special attention to the try/except block in the
get_number_dict() method:
#!/usr/bin/env python
import pdb
import string
import sys
class ConvertToDict:
def __init__(self):
self.tmp_dict = {}
self.return_dict = {}
def walk_string(self, some_string):
'''walk given text string and return a dictionary.
Maintain state in instance attributes in case we hit an exception'''
l = string.split(some_string)
for i in range(len(l)):
key = str(i)
self.tmp_dict[key] = int(l[i])
return_dict = self.tmp_dict
self.return_dict = self.tmp_dict
self.reset()
return return_dict
def reset(self):
'''clean up'''
self.tmp_dict = {}
self.return_dict = {}
def get_number_dict(self, some_string):
'''do super duper exception handling here'''
try:
return self.walk_string(some_string)
except:
#modified exception handler - drop us into a debugger
tb = sys.exc_info()[2]
pdb.post_mortem(tb)
#if we hit an exception, we can rely on tmp_dict
being a backup to the point of the exception
return self.tmp_dict
def main():
ctd = ConvertToDict()
for line in file(sys.argv[1]):
line = line.strip()
print "*" * 40
print "line>>", line
print ctd.get_number_dict(line)
print "*" * 40
if __name__ == "__main__":
main()
I chose to kick off the postmortem debugger in the except clause of the
get_number_dict() method, because get_number_dict() is
the closest exception handler to where the exception must be occurring in the
walk_string() method. Here is the result of a run with a
list immediately after it for context:
jmjones@bean:~/debugger $ python example_debugger_pm.py example_debugger.data
****************************************
line>> 1 2 3 4 5 6 7 8 9 10
{'1': 2, '0': 1, '3': 4, '2': 3, '5': 6, '4': 5, '7': 8, '6': 7, '9': \
10, '8': 9}
****************************************
****************************************
line>> 1 2 3 4 5 6 7 8 9 10
> /home/jmjones/debugger/example_debugger_pm.py(17)walk_string()
-> self.tmp_dict[key] = int(l[i])
(Pdb) list
12 '''walk given text string and return a dictionary.
13 Maintain state in instance attributes in case
we hit an exception'''
14 l = string.split(some_string)
15 for i in range(len(l)):
16 key = str(i)
17 -> self.tmp_dict[key] = int(l[i])
18 return_dict = self.tmp_dict
19 self.return_dict = self.tmp_dict
20 self.reset()
21 return return_dict
22 def reset(self):
This confirmed some of my earlier suspicions. It was hitting an
exception in the for loop. Now my goal was to figure out what
exception it hit and why:
(Pdb) for e in sys.exc_info(): print "EXCEPTION>>>", e
EXCEPTION>>> exceptions.AttributeError
EXCEPTION>>> Pdb instance has no attribute 'do_for'
EXCEPTION>>> <traceback object at 0x402ef784>
What? My code was not even calling a do_for() method or trying
to access any do_for attribute. This is a little gotcha that
hounded me for a bit as I was trying to print the last exception from within
this example code. You should be able to tell what is going on with a little
insight from the traceback module:
(Pdb) import traceback
(Pdb) traceback.print_stack()
File "example_debugger_pm.py", line 48, in ?
main()
File "example_debugger_pm.py", line 44, in main
print ctd.get_number_dict(line)
File "example_debugger_pm.py", line 33, in get_number_dict
pdb.post_mortem(tb)
File "/usr/local/python24/lib/python2.4/pdb.py", line 1009, in post_mortem
p.interaction(t.tb_frame, t)
File "/usr/local/python24/lib/python2.4/pdb.py", line 158, in interaction
self.cmdloop()
File "/usr/local/python24/lib/python2.4/cmd.py", line 142, in cmdloop
stop = self.onecmd(line)
File "/usr/local/python24/lib/python2.4/cmd.py", line 218, in onecmd
return self.default(line)
File "/usr/local/python24/lib/python2.4/pdb.py", line 167, in default
exec code in globals, locals
File "<stdin>", line 1, in ?
Basically, the Python interpreter keeps track of any exceptions that running
code raises; it saves the last traceback when running code hits an exception.
The debugger is just another piece of code that the interpreter runs. When a
user feeds the debugger commands, those commands may raise exceptions within
the debugger itself. In this case, I gave the debugger a for
statement. It first tried to execute a debugger for command by
calling the do_for() method. That raised the exception above.
Such debugger exceptions can potentially pollute the interpreter with
unexpected exceptions and tracebacks that we users don't really want to see
when trying to debug our own code. Maybe there is a better way of writing a
debugger than combining the debugger code and the debugged code in the same
interpreter as shown here, but the Python team has produced a pretty tricky,
complex piece of code that otherwise works really well.