pdb
You can enter the python debugger by simply adding breakpoint()
to your code. From there you have some super helpful commands at your fingertips:
n
- Execute the current line of code and move to the next line.
s
- Step into the function you are about to execute. This is helpful if you have put your breakpoint quite high in the code hierarchy, but your issue happens within one of the functions called.
c
- Continue until the next breakpoint.
u
- Navigate up the stack. This is massively helpful if your bug rises in one section of the code, but was caused by an earlier problem. You can then navigate back down with d
b
- Add a breakpoint without exiting the debugger. This is helpful if you are exploring the code. You can add breakpoints by referencing the line number of the current file (b 52
), referencing the file and line number (b utils:52
) or the function name (b my_broken_function
).
r
- Execute the remainder of the function you are in and continue debugging in the function "one up" in the stack.
args
- See the arguments provided to the function that you are in (more helpful than using python's built in locals()
).
?
- Putting a question mark after a variable will give you high-level information about that object.
pp
- Pretty print a variable. This is helpful for large JSON or nested objects.
source
- View the source code for a function or a class.
⏎
- Hitting return will re-execute the most recently used command (helpful when repeating a command until you get to the right place).
q
- Exit the debugger! This will...
Some less useful commands:
l
- See where you are in the code.
j
- Jump to a specific line in the code.
pdbpp
Reasons you will want pdbpp:
- Tab complete - Enough said.
ll
- See where you are in a larger section of the code, typically the whole function.
sticky
- This command shows where you are in the code at all times, painting the lines of code for the function you are in onto your screen.
- Pretty colours... 🙄
.pdbrc
You can configure your pdb setup with .pdbrc
and .pdbrc.py
files either at the roof of your project or at ~/.pdbrc.py
.
You can add custom aliases to help with debugging in your .pdbrc
file. For example, the below allows you to quickly print an object's dictionary representation:
alias ppd print(%1.__dict__)
I'm a big fan of sticky
mode. You can enable this by default by adding the following to your .pdbrc.py
file:
import pdb
class Config(pdb.DefaultConfig):
sticky_by_default = True
Pytest
Pdb and consequently pdbpp works nicely with other tools. Pytest has a --pdb
flag that puts you into a python debugger if a test fails (so you can dynamically explore the cause of the error). If your test isn't failing but is behaving badly you can enter a pdb prompt at the very top of your test using --trace
.
Examples:
-
pytest tests/my_module/test_specific_thing -k misbehaving_test.py --trace
Enter a pdb shell at the top of the test you want to debug.
-
pytest tests/my_module/test_specific_thing -k this_test_shouuuuuld_be_fine.py --pdb
Enter a pdb shell wherever the test fails.
I can't edit the source code, so I can't add a breakpoint
! Help!
If you are unable to edit the code meaning you can't add a breakpoint you have two options:
- use
python -m pdb my/questionable/code.py
- use
ipdb
python -m pdb my/questionable/code.py
You can run a python script as "main" using pdb. This immediately drops you in a pdb debug shell meaning you can then navigate your code with the normal commands (such as n
and c
). If you need to add a breakpoint you can use the b
functionality to specify where you want to stop.
Conditional breakpoints
If you're working in production it's likely that you are dealing with inputs much larger than you would like (e.g. for
loops with far more entries than you can manually run through). Consequently (given you can't edit the code) it can be really handy to add conditional breakpoints. Simply use the normal b
and line number syntax, add a comma, then enter the condition you want break on.
b 22, datapoint.url == "problematic_url" # break on line 22, but only if the datapoint has the problematic URL
b 100, datapoint.size > MAX_SIZE # break on line 100, but only when specific attributes are problematic
start an interactive ipdb prompt
If you don't have a tidy pre-defined entrypoint that you can use (for example your code is bundled together with a single entrypoint that is used by a webserver like django or flask) you can instead start an interactive terminal (I would recommend ipython
) and then piece together the functionality you need. You can do this by importing functions from your source code. You can then execute functions using the pdb
debugger with runcall
(or similar):
import pdb
pdb.runcall(my_func, arg1, arg2, arg3)