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.