Debugging

In general, bugs should be handled by the exceptions mechanism provided by Python and programmed (in your code). However, even using them, some unexpected bugs could exist, making that your module crashes. In other occasions, your program does not work as you expected and you want to figure out what is happenning.

Whatever the reason is (module crashings or algorithmic issues), Python provides a debugging system for making your programming experience happier. Basically, you need to run (importing it) the debugger when you are running your module. There exists two debugging modules: pdb and ipdb. The main difference between them is that the last one is more humman-friendly.

1. [0] Invoking the debugger from the shell line

python -m pdb debug_me.py

pdb commands

Press h for help. Commands:

  1. [l]ist {<first line> {,<last line>}}: lists the related portion of code.
  2. [w]here: shows the execution stack.
  3. [b]reak {{{file:}<line> | <function>} {, <condition>}}: without argument, shows the breakpoints; with argument, sets a (if specified, conditional) breakpoint.
  4. tbreak {{{file:}<line> | <function>} {, <condition>}}: define a temporal breakpoint (only survives one hit).
  5. disable <breakpoint number> {<breakpoint number> ...}: disable a breakpoint.
  6. enable <breakpoint number> {<breakpoint number> ...}: enables a breakpoint.
  7. [cl]ear {<break point number> {<break point number> ...}}: deletes a breakpoint.
  8. condition <breakpoint number> <condition>: defines a condition for a breakpoint.
  9. ignore <breakpoint number> <count>: ignores a breakpoint for a number of hits.
  10. [[c]ont]inue: continues the execution.
  11. [n]ext: runs the following instruction (don't enter functions).
  12. [s]tep: runs the following instruction (enter functions).
  13. [unt]il: continues until execution reaches a line in the same function with a line number higher than the current value.
  14. [u]p: moves the current frame one level up in the stack trace (to an older frame).
  15. [d]own: moves the current frame one level down in the stack trace (to a newer frame).
  16. [run] {args}: (re)run the script.
  17. [p]rint (<object>): prints an object.
  18. [p]rety [p]rint (<object>): prints an object.
  19. [a]rgs: prints the arguments of the current funcion.
  20. commands <breakpoint number> ... end: define a set of debugging commands to be run each time a breakpoint is reached.
  21. alias {<name> {<command> {<parameter> <parameter> ...}}}: show/define an alias for a pdb command.
  22. !: run a Python expression.

Example:


In [ ]:
! cat debug_me.py

Run it in a shell.

(yapt) $ python -m pdb debug_me.py
> /Users/vruiz/python-tutorial/debug_me.py(6)<module>()
-> def call_me():
    (Pdb) c <------------------ YOUR INTERACTION HERE ------------------------
Debug me with "python -i debug_me.py"
After the run-time error, you should have access to the "a" variable
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.5.2_3/Frameworks/Python.framework/Versions/3.5/lib/python3.5/pdb.py", line 1661, in main
    pdb._runscript(mainpyfile)
  File "/usr/local/Cellar/python3/3.5.2_3/Frameworks/Python.framework/Versions/3.5/lib/python3.5/pdb.py", line 1542, in _runscript
    self.run(statement)
  File "/usr/local/Cellar/python3/3.5.2_3/Frameworks/Python.framework/Versions/3.5/lib/python3.5/bdb.py", line 431, in run
    exec(cmd, globals, locals)
  File "<string>", line 1, in <module>
  File "/Users/vruiz/python-tutorial/debug_me.py", line 6, in <module>
    def call_me():
  File "/Users/vruiz/python-tutorial/debug_me.py", line 11, in call_me
    d=1/c
ZeroDivisionError: division by zero
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program
> /Users/vruiz/python-tutorial/debug_me.py(11)call_me()
-> d=1/c
(Pdb) l <--------------------- YOUR INTERACTION HERE -----------------------
  6     def call_me():
  7         print("Debug me with \"python -i debug_me.py\"")
  8         print("After the run-time error, you should have access to the \"a\" variable")
  9         a=1
 10         c=0
 11  ->     d=1/c
 12         print("This code is never reached")
 13         a=2
 14     
 15     b=1
 16     
(Pdb) p c <--------------------- YOUR INTERACTION HERE ------------------------
0
(Pdb) quit <--------------------- YOUR INTERACTION HERE ------------------------
Post mortem debugger finished. The debug_me.py will be restarted
> /Users/vruiz/python-tutorial/debug_me.py(6)<module>()
-> def call_me():
(Pdb) quit <--------------------- YOUR INTERACTION HERE ------------------------

2. [1] Invoking the debugger from the source code

Example:


In [ ]:
! cat debug_me_2.py

Run me in a shell.

(yapy) $ python debug_me_2.py
Debug me with "python -i debug_me.py"
After the run-time error, you should have access to the "a" variable
> /Users/vruiz/YAPT/debug_me_2.py(12)call_me()
     11     import ipdb; ipdb.set_trace()
---> 12     d=1/c
     13     print("This code is never reached")

ipdb> p c <----------------- YOUR INTERACTION HERE -----------------
0
ipdb> quit  <----------------- YOUR INTERACTION HERE -----------------
Exiting debugger.

3. [1] Invoking the debugger in "post-mortem" mode

"Post-mortem" debugging basically means that you want to debug after a module crash. Does not exist a pure post-mortem debugging in Python (in the sense you executed your code in the normal way, i.e. not expecting the bug, and after that, you must run the debugger). Basically, in Python you have two alternatives:

3.1. Using the -i parameter when running Python:

(inspect interactively after running script)

Example:


In [ ]:
!cat debug_me.py

Run:

(yapt) $ python -i debug_me.py
After the run-time error, you should have access to the "a" variable
Traceback (most recent call last):
  File "debug_me.py", line 14, in <module>
    call_me()
  File "debug_me.py", line 8, in call_me
    d=1/c
ZeroDivisionError: division by zero
>>> import pdb <------------------ YOUR INTERACTION HERE -------------------
>>> pdb.pm() <------------------ YOUR INTERACTION HERE -------------------
> /Users/vruiz/YAPT/debug_me.py(8)call_me()
-> d=1/c
(Pdb) p c <------------------ YOUR INTERACTION HERE -------------------
0
(Pdb) quit <------------------ YOUR INTERACTION HERE -------------------
>>> quit() <------------------ YOUR INTERACTION HERE -------------------

3.2. Importing your module and then, calling the debugger

Example:

(yapt) $ python
>>> import pdb  <------------------ YOUR INTERACTION HERE -------------------
>>> import debug_me <------------------ YOUR INTERACTION HERE -------------------
Debug me with "python -i debug_me.py"
After the run-time error, you should have access to the "a" variable
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/vruiz/YAPT/debug_me.py", line 14, in <module>
    call_me()
  File "/Users/vruiz/YAPT/debug_me.py", line 8, in call_me
    d=1/c
ZeroDivisionError: division by zero
>>> pdb.pm() <------------------ YOUR INTERACTION HERE -------------------
> /Users/vruiz/YAPT/debug_me.py(8)call_me()
-> d=1/c
(Pdb) p c <------------------ YOUR INTERACTION HERE -------------------
0
(Pdb) quit <------------------ YOUR INTERACTION HERE -------------------
>>> quit() <------------------ YOUR INTERACTION HERE -------------------

3.3 Using IPython's %debug

After an exception occurs, you can call %debug to jump into the Python debugger (pdb) and examine the problem.


In [ ]:
%run debug_me.py

In [ ]:
%debug

(write quit() to exit the debugger)

3.4 Using IPython's %run


In [ ]:
%run -d debug_me.py