Debugging with pdb (the python debugger)
Often it is useful to use an interactive debugger to step through code and examine variable states when trying to solve tricky coding problems. Python provides pdb for exactly this purpose.
To set a simple breakpoint, you add the following line to your code where you want the breakpoint to occur:
import pdb; pdb.set_trace()
After you've put this line in, then you need to stop Zope and restart it using bin/instance fg. Then perform the steps needed to execute the code with your breakpoint. The terminal where you started Zope should then pop you into a debugging session with the following prompt:
-> Pdb().set_trace() (Pdb)
At this point, you can type r to 'r'eturn from the set_trace() call to the method where you inserted the breakpoint, and you can start stepping through and examining your environment. More information about how to use the debugger can be found in the python documentation.
Occasionally a breakpoint is not ideal, especially when there's an error happening in some code that is executed frequently. You don't want to have to hit the breakpoint 50 times before you get to the actual error condition. For that, we use the pdb post-mortem idiom:
try:
[YOUR CODE HERE]
except:
import pdb, sys
e, m, tb = sys.exc_info()
pdb.post_mortem(tb)
Then, again, you restart Zope using bin/instance fg. If an error is raised in the [YOUR CODE HERE] section, then python will pop you into a debugger session in your method and you will be able to examine the stack trace and all of the variables just as if you had set a breakpoint.
Don't forget to remove the debugging hooks from your code before you commit. Also, sometimes when you use pdb, you may find that ctrl-c or your usual method of quitting Zope from the console stops working. In that case, from another shell you can use kill -9 `cat $INSTANCE_HOME/var/Z2.pid` to kill the Zope process. Don't forget the backtick quotes around the cat statement. This command terminates the process with a -9 signal, which means, quit no matter what.
Finally, for an added treat, you can try entering a pdb session from a shell within an Emacs session. If you have python-mode installed, you may find that Emacs has a few surprises in store for you.
For a full treatment of using the Python debugger with Zope, check out Ken Manheimer's oldie-but-a-goodie Conversing with Zope on zope.org. It is an absolute must-read if you are having problems with a non-responsive Zope.
Useful trick for stopping different processes from calling set_trace
Sometimes, while you are in the middle of debugging, Zope will try to reload a page because your debugging makes it take too long, and a breakpoint set_trace will fire up again in a parallel process. Or, someone else will open up a page that makes set_trace fire up again.
This gets very confusing, for instance in the debug session, hitting "l" (to view the current file the code is running in) will bounce between different files.
One way to paritally get around this is code like the following:
if not globals().get( 'PDB_ACTIVE', 0 ):
globals()['PDB_ACTIVE'] = 1
import pdb; pdb.set_trace()
What this does is make sure the same pdb breakpoint doesn't fire twice in the same debug session. After the first time it fires, it adds a variable named "PDB_ACTIVE" into the module with the breakpoint; the next time it thinks of breaking, it first sees that "PDB_ACTIVE" is present and so it does not fire of the breakpoint.
This isn't always what you want, but it can help sometimes. (For instance when debugging a VirtualHostMonster issue: while debugging the page kept trying to reload and throwing off the debugger.)
PDB tutorials
- Debugging in Python, by Stephen Ferg. A short and practical pdb tutorial.
- Interactive Debugging in Python, by Jeremy Jones. A longer and deeper tutorial, with advanced examples.
