Thursday 6 August 2009

Understanding Python Error Messages

Understanding runtime errors and (uncaught) exceptions in any programming language can be a pain, especially if your code is complex or the error message is obscure. The usual way to deal with this situation is either to use a full blown debugger to step through the code, or to add as many print statements as necessary to uncover the source of the error. However, Python provides a third solution which is pretty neat -- use a disassembler. The dis module takes a Python bytecode object (as generated by the builtin compile function or the py_compile module) and prints out a listing of the bytecode instructions "in" that object. However, dis also has another use -- calling the disassembler with no arguments prints out the bytecode instructions generated during the last traceback.

For example, if you import dis in the interactive interpreter and generate a traceback, like this:

>>> 'foobar' * 2.5
Traceback (most recent call last):
 File "", line 1, in 
TypeError: can't multiply sequence by non-int of type 'float'
>>>

You can then run the dis.dis() method to examine the error:

>>> dis.dis()
 1           0 LOAD_CONST               0 ('foobar')
             3 LOAD_CONST               1 (2.5)
   -->       6 BINARY_MULTIPLY
             7 PRINT_EXPR
             8 LOAD_CONST               2 (None)
            11 RETURN_VALUE
>>>

The arrow on the left (-->) points to the bytecode instruction which caused the TypeError. The number 0 on the left before load_const shows the line number of the source which generated the load_const bytecode instruction. On the right hand side in brackets are the constants loaded into the interpreter.