Debugging Python Code
People on my team asked me some time ago how I debug things in our python code base. So I thought I’d share here.
The easiest (and least efficient) way to debug is to use print statements and
logging. But since you’re not using a real debugger, you need to update the
code and rerun in order to get new results.
Hence the most efficient way to debug things in python is to use a debugger. Don’t be scared, they are easy to master and they’ll serve you nicely for the rest of your life. They all are very similar.
Debuggers, intro
I didn’t know that python 3.7 supports breakpointing natively, thanks to Hunor who gave me pointers.
In this blog post, I’ll be using packit, our rockstar project.
How do I start?
I’d just put breakpoint() statement in the code where I want to prompt for the debugger.
And then just run the code:
$ packit srpm
Packit 0.9.1.dev2+gd4f6f93 is being used.
> /home/tt/g/user-cont/packit/packit/api.py(446)create_srpm()
-> self.up.run_action(actions=ActionName.post_upstream_clone)
(Pdb)
But how?
(Pdb) h
Documented commands (type help <topic>):
========================================
EOF c d h list q rv undisplay
a cl debug help ll quit s unt
alias clear disable ignore longlist r source until
args commands display interact n restart step up
b condition down j next return tbreak w
break cont enable jump p retval u whatis
bt continue exit l pp run unalias where
Miscellaneous help topics:
==========================
exec pdb
See all the one-letter commands? A genius did that.
Let’s see what the l command does.
(Pdb) h l
l(ist) [first [,last] | .]
List source code for the current file. Without arguments,
list 11 lines around the current line or continue the previous
listing. With . as argument, list 11 lines around the current
line. With one argument, list 11 lines starting at that line.
With two arguments, list the given range; if the second
argument is less than the first, it is a count.
The current line in the current frame is indicated by "->".
If an exception is being debugged, the line where the
exception was originally raised or propagated is indicated by
">>", if it differs from the current line.
(Pdb)
Really?
(Pdb) l
441 :param output_file: path + filename where the srpm should be written, defaults to cwd
442 :param srpm_dir: path to the directory where the srpm is meant to be placed
443 :return: a path to the srpm
444 """
445 breakpoint()
446 -> self.up.run_action(actions=ActionName.post_upstream_clone)
447
448 try:
449 self.up.prepare_upstream_for_srpm_creation(upstream_ref=upstream_ref)
450 except Exception as ex:
451 raise PackitSRPMException(
Neat.
Debuggers, level 2
pdb debugger is good, but ipdb is even better - it has colors and completion.
$ packit srpm
Packit 0.9.1.dev2+gd4f6f93 is being used.
> /home/tt/g/user-cont/packit/packit/api.py(446)create_srpm()
445 import ipdb; ipdb.set_trace()
--> 446 self.up.run_action(actions=ActionName.post_upstream_clone)
447
ipdb> l
441 :param output_file: path + filename where the srpm should be written, defaults to cwd
442 :param srpm_dir: path to the directory where the srpm is meant to be placed
443 :return: a path to the srpm
444 """
445 import ipdb; ipdb.set_trace()
--> 446 self.up.run_action(actions=ActionName.post_upstream_clone)
447
448 try:
449 self.up.prepare_upstream_for_srpm_creation(upstream_ref=upstream_ref)
450 except Exception as ex:
451 raise PackitSRPMException(
ipdb>
The output is now colored. Trust me, I’m an engineer.
What commands should you know?
n— execute current statement and go to the next onew— where are we in the call stack?u— go one level up in the stackd— guess (yes, it’s to go one level down)l— you’ve seen this one already (show code)c— continue the execution (until next breakpoint or the end of the program)s— step into current function (similar tod)q— quit the program right nowb— set a beerpoint, I mean breakpoint
Debuggers, level 99
Let’s talk about some tips & tricks:
-
Always put breakpoints as close as possible to the place where you think is a problem (also on the proper level of the call stack), so you don’t spend too much time of doing
n,s,u,d… -
When using pytest, run it with
-sbecause pytest captures stdin and your test would fail withOSError: reading from stdin while output is captured. -
When in debugger, run real python code: print things, define new vars, override things, go bananas.
-
It’s efficient to use multiple breakpoints and
cbetween them. -
You can debug any python code like this: in case of a traceback, just put breakpoint in the place where the exception happened, rerun and check what’s wrong.
Have fun!