How I Debug My Code
March 6, 2017
The more code you write, the more time you also spend tracking down errors. That's why it's worthwhile to learn some debugging-skills!
Pre-empting errors
Do as much as possible upfront to facilitate discovery of bugs. First of all, if you are still printing info to the console, you should look into some logging practices.
You can also employ your editor to highlight possible mistakes. These often sneak into Python code unnoticed because of Python's interpreted nature (no compilation) and only optional typing support. A first stop when I write code is to employ a few static code analysis tools. They can help you spot for example broken imports and variables referenced before assignment. It should be fairly simple to integrate them in your editor to run every time you save a file!
UPDATE! As suggested by @joeyespo you can also integrate flake8 with py.test using a handy plugin. All you need to do is install and run:
pip install pytest-flake8
py.test --flake8
Breakpoints
Logging + linting provide a nice start but sometimes you need more dedicated tools to do resolve bugs. When I run into an unforeseen issue, my first stop is to interactively investigate the code by setting a breakpoint. Now Python's interpreted nature becomes an asset! You can use the built-in pdb
module but just like ipython
replaces the standard python
shell, you really should be using ipdb
! Start by installing it:
pip install ipdb
Now you can add this line anywhere in your code where you want to pause execution and do some interactive exploration:
>>> import ipdb; ipdb.set_trace()
Don't worry about the awkward syntax. The important thing to understand is that when the interpreter reaches this line of code, you will be dropped into an ipython
session where you can play around with variables and functions just as you'd expect.
❯ chanjo db setup
> /Users/demo/projects/chanjo/chanjo/store/cli.py(29)setup()
27 log.info('setting up new database')
28 import ipdb; ipdb.set_trace()
---> 29 context.obj['db'].set_up()
30
31
ipdb> context.obj
{'db': <chanjo.store.api.ChanjoDB object at 0x103e05f10>,
'database': None}
ipdb>
You navigate around using a few special commands. These are the ones I use 99% of the time:
n(ext)
: execute the current line of codes(tep)
: step into a functionu(p)
: step out of the function into the parent scopec(ontinue)
: execute code until the next exception/breakpoint/endexit
: directly exit out of the shell
You can find a full list of commands by typing help
.
Post mortem-mode / Debugging in iPython
Early on in development I spend a lot of time in iPython importing my functions and testing them out. You can use some very handy magic function to facilitate debugging. First is %debug
. When you run into an exception you can simply type in this command to activate the debugger (ipdb) and inspect the stack frame just after it exited. You can automate this to always be the case by instead enabling %pdb
mode. Then every time some exception gets thrown you will be dropped into an interactive debugger.
Summary
Among my developer colleagues and friends, linters aren't particularly popular but I still believe they've helped me discover a lot of bugs and reminded me of best practices. When an issue slips through, I pull out my ipdb power tool to attack the problem in an interactive shell.
Credits
hydra by Huu Nguyen from the Noun Project
Bug by ProSymbols from the Noun Project
crossed swords by Misha Petrishchev from the Noun Project