Python: More Precise Exception Messages

Posted at March 21, 2014

I have been writing a lot of Python code lately. One of aspects of Python that has always bugged me was that debugging exception has never been as easy as it should be. In my python apps, I usually wrap the body of the def main(): in a try: except: pair so that I catch all unhandled exceptions before the application is aborted. While I am writing code, I also put a pdb.set_trace() in the except: block so that exceptions drop me into the debugger. The only problem is, by the time I get into the debugger, the stack has been unwound by the exception processsing code. So if I dump the backtrace to find where the exception came from, I am out of luck and will see something like this:

-> load_entry_point('demo==0.1', 'console_scripts', 'demo')()
-> /usr/lib/python2.7/dist-packages/demo-0.1-py2.7.egg/demo/demo.py(61)main()
-> print("\nERROR: %s\n" % e)

That is not much help. If I then take a look at the exception instance, I will find that there isn't much in there either. To make things worse, the string conversion of the exceptions are typically devoid of any information about their origin. For instance, access to a non-existent member of a class will throw an exception with the message:

'MyDemoClass' object has no attribute 'myattr'

True, the class MyDemoClass has no attribute myattr, but where in my code am I trying to access that member? Recently, I learned about the inspect module which gives me access to the runtime environment and can help in these situations. By using it in the except: block, I can turn the anemic error messages into a much more precise and helpful message like so:

Exception:
  from /usr/lib/python2.7/site-packages/demo-0.1-py2.7.egg/demo/demo.py, line 63: 
    'MyDemoClass' object has no attribute 'myattr'

With the new error message, not only does it tell me the exception error string, but it also reports the exact file and line that the exception was thrown from. Now that is helpful. You can do it too. The gist below shows you how. This is now a permanent part of my python app skeleton code: