including column information when an exception occurs

Tim Peters tim_one at email.msn.com
Sat Dec 4 17:12:21 EST 1999


[Michael McCandless]
> When a Python exception occurs, the interpreter shows you the stack
> traceback to the line where the error occurred.  Why, when it gets down
> to the actual line, does it not also report, eg, the column or
> subexpression in which the error occurs?

It does, but only if the exception is a Syntaxrror.  Column information is
available while the program is being parsed (the only time you can get a
SyntaxError), but is not available at runtime (which is when almost all
other exceptions get raised).  The interpreter incurs a good deal of pain to
keep the line number available at runtime, but even that's limited (e.g., if
an exception occurs in an expression spanning multiple lines, you only get
the number of the line that begins the stmt that contains the expression).

The easiest way to get more context is probably to disassemble the byte code
near the point of failure.  For example, putting the attached in module
tbrev.py and running your test case like so:

def test0(tup, i0, i1):
    test1(tup, i0, i1)

def test1(tup, i0, i1):
    return tup[i0] + tup[i1]

try:
    test0((1, 2, 3), 2, 4)
except:
    from misc import tbrev # import from where you store it
    tbrev.show_context()

prints this new line before the usual traceback:

    Probably near a reference to local variable i1,

That wouldn't do you a lot of good if the failing code looked like

    tup1[i] + tup2[i]

instead!  Disassembling more context gets messier.  This is a job for
Michael Hudson <0.9 wink>.

if-python-were-written-in-python-it-would-be-easier-ly y'rs  - tim

import dis
LOAD_GLOBAL = dis.opname.index('LOAD_GLOBAL')
LOAD_FAST   = dis.opname.index('LOAD_FAST')
LOAD_ATTR   = dis.opname.index('LOAD_ATTR')
LOADOPS = (LOAD_GLOBAL, LOAD_FAST, LOAD_ATTR)
HAVE_ARGUMENT = dis.HAVE_ARGUMENT
del dis

def show_context():
    import sys
    tb = sys.exc_info()[2]
    if tb is None:
        return
    while tb.tb_next:
        tb = tb.tb_next
    lasti = tb.tb_lasti
    code = tb.tb_frame.f_code
    bytecode = code.co_code
    i = 0
    limit = min(lasti, len(bytecode))
    loads = []
    while i < limit:
        c = bytecode[i]
        op = ord(c)
        i = i+1
        if op >= HAVE_ARGUMENT:
            oparg = ord(bytecode[i]) + ord(bytecode[i+1])*256
            i = i+2
            if op in LOADOPS:
                loads.append((op, oparg))
    if loads:
        op, arg = loads[-1]
        if op == LOAD_GLOBAL:
            msg = "global variable " + code.co_names[arg]
        elif op == LOAD_FAST:
            msg = "local variable " + code.co_varnames[arg]
        elif op == LOAD_ATTR:
            msg = "attribute " + code.co_names[arg]
        else:
            msg = "<OOPS! I got confused>"
        print "Probably near a reference to", msg + ","
    raise






More information about the Python-list mailing list