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?
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
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.
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
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With