Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I extract local variables from a stack trace?

Tags:

python

pdb

ipdb

Suppose I have a function that raises unexpected exceptions, so I wrap it in ipdb:

def boom(x, y):
    try:
        x / y
    except Exception as e:
        import ipdb; ipdb.set_trace()

def main():
    x = 2
    y = 0
    boom(x, y)

if __name__ == '__main__':
    main()

I can move up the stack to find out what values x and y have:

$ python crash.py 
> /tmp/crash.py(6)boom()
      5     except Exception as e:
----> 6         import ipdb; ipdb.set_trace()
      7 

ipdb> u
> /tmp/crash.py(11)main()
     10     y = 0
---> 11     boom(x, y)
     12 

ipdb> p y
0

However, when debugging, I want to just put a debugger at the top level:

def boom(x, y):
    x / y

def main():
    x = 2
    y = 0
    boom(x, y)

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        import ipdb; ipdb.set_trace()

I can display the traceback, but I can't view the variables inside the function called:

$ python crash.py 
> /tmp/crash.py(14)<module>()
     12         main()
     13     except Exception as e:
---> 14         import ipdb; ipdb.set_trace()

ipdb> !import traceback; traceback.print_exc(e)
Traceback (most recent call last):
  File "crash.py", line 12, in <module>
    main()
  File "crash.py", line 8, in main
    boom(x, y)
  File "crash.py", line 3, in boom
    x / y
ZeroDivisionError: integer division or modulo by zero
ipdb> d # I want to see what value x and y had!
*** Newest frame

The exception object clearly still has references to the stack when the exception occurred. Can I access x and y here, even though the stack has unwound?

like image 824
Wilfred Hughes Avatar asked Apr 12 '16 16:04

Wilfred Hughes


3 Answers

Depending on what you need, there are 2 general best practices.

Just print the variables with minimal code edits

Have a look at some related packages. For simple usage you might pick traceback-with-variables (pip install traceback-with-variables), here is it's postcard

enter image description here

Or try tbvaccine, or better-exceptions, or any other package

Programmatically access variables to use them in your code

Use inspect module

except ... as ...:
    x = inspect.trace()[-1][0].f_locals['x']

What about debugger?

Debugger is made for step-by-step execution and breakpoints. Using it to inspect exception reasons is really inconvenient and should be avoided. You can automate your debug session using two mentioned best practices.

like image 70
Kroshka Kartoshka Avatar answered Oct 24 '22 02:10

Kroshka Kartoshka


Turns out that it is possible to extract variables from a traceback object.

To manually extract values:

ipdb> !import sys
ipdb> !tb = sys.exc_info()[2]
ipdb> p tb.tb_next.tb_frame.f_locals
{'y': 0, 'x': 2}

Even better, you can use an exception to explicitly do post-mortem debugging on that stack:

import sys

def boom(x, y):
    x / y

def main():
    x = 2
    y = 0
    boom(x, y)

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        # Most debuggers allow you to just do .post_mortem()
        # but see https://github.com/gotcha/ipdb/pull/94
        tb = sys.exc_info()[2]
        import ipdb; ipdb.post_mortem(tb)

Which gets us straight to the offending code:

> /tmp/crash.py(4)boom()
      3 def boom(x, y):
----> 4     x / y
      5 

ipdb> p x
2
like image 26
Wilfred Hughes Avatar answered Oct 24 '22 02:10

Wilfred Hughes


You can also use the context manager

with ipdb.launch_ipdb_on_exception():
    main()

It's an easy-to-use wrapper using ipdb.post_mortem.

like image 28
Hot.PxL Avatar answered Oct 24 '22 03:10

Hot.PxL