Debug a Python Script from the Terminal with pdb3.12
Print statements only get you so far, and pdb3.12 is Python's built-in way to actually stop a script and poke at it from a plain terminal. It runs a Python 3.12 program under the standard debugger, lets you stop at a useful line, inspect variables, step through code, and leave cleanly. It's a thin wrapper around the pdb module, so nothing beyond your existing Python install is needed.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about 15 minutes for the first run. You need a shell, Python source you can run, and permission to read it. Run these as the account that owns the project: sudo can change the environment and hide the very problem you're trying to inspect.
Before you start: make a reproducible example
Start with something small so the debugger's output is easy to recognise. This example deliberately divides by zero when the supplied value is zero.
mkdir -p /tmp/pdb-demo
cat > /tmp/pdb-demo/calculate.py <<'PY'
def ratio(total, count):
return total / count
value = ratio(10, 0)
print(value)
PY
cd /tmp/pdb-demo
python3.12 calculate.py
The ordinary run ends with a traceback containing ZeroDivisionError. The file is temporary, and nothing outside /tmp/pdb-demo changes. If you're debugging a real project, substitute its path from here on.
1. Start pdb3.12 on the script
Pass the script as the first non-option argument. The debugger stops before the first executable line, giving you a chance to set a breakpoint before anything runs.
cd /tmp/pdb-demo
pdb3.12 calculate.py
You should see a prompt like this:
> /tmp/pdb-demo/calculate.py(1)<module>()
-> def ratio(total, count):
(Pdb)
The exact path and line display can vary. That (Pdb) prompt is the checkpoint: debugger commands go there, not at your normal shell prompt. Type help for the command list, or help break for help on one command.
2. Set a breakpoint and continue
Set a breakpoint on the division line, then let the program run. A breakpoint can name a line in the current file or a function; b is short for break.
(Pdb) break 2
Breakpoint 1 at /tmp/pdb-demo/calculate.py:2
(Pdb) continue
> /tmp/pdb-demo/calculate.py(2)ratio()
-> return total / count
(Pdb)
At this point the function has been entered but the division hasn't run yet. To check what's about to happen, print the arguments:
(Pdb) p total
10
(Pdb) p count
0
p expression evaluates an expression in the current stack frame. pp expression pretty-prints a larger value. Evaluation is real Python code, so avoid calling functions that mutate data or contact external services just to inspect them.
3. Move through the code without losing your place
Use next to run the current line and stop at the next one in the same function. Use step to enter a called function instead. Use list to show nearby source, and where for the call stack.
(Pdb) list
1 def ratio(total, count):
2 B-> return total / count
3
(Pdb) where
/tmp/pdb-demo/calculate.py(4)<module>()
-> value = ratio(10, 0)
/tmp/pdb-demo/calculate.py(2)ratio()
-> return total / count
In that listing, the arrow marks the current line and B flags a breakpoint. If the next statement is an ordinary call, next runs it without stopping inside; step goes in. A blank line repeats your last command, so be careful after anything that resumes execution.
4. Inspect a failure after it happens
Continue from the breakpoint. The division now raises the exception, and pdb drops into a post-mortem session:
(Pdb) continue
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
Uncaught exception. Entering post mortem debugging
> /tmp/pdb-demo/calculate.py(2)ratio()
-> return total / count
(Pdb)
Use p, where, and up or down to examine the failing frame and its callers. The debugger doesn't fix the exception for you; it lets you confirm the state that caused it. For a repeatable stop inside application code, add a temporary breakpoint(), or use import pdb; pdb.set_trace() where you need compatibility with older Python versions.
5. Leave the session and remove temporary changes
Use quit when you want to abort the debugged program outright. This is the safety boundary worth remembering: unlike continue, quit stops the target immediately. If the program has already finished, pdb may offer to restart it while keeping breakpoints; answer quit once you're done.
(Pdb) quit
Remove a breakpoint without touching the source:
(Pdb) clear 1
Deleted breakpoint 1
If you dropped breakpoint() or pdb.set_trace() into a real file, take that line out before committing or deploying. To clean up just the demo directory, use this exact path once you've confirmed it holds nothing you need:
rm -rf -- /tmp/pdb-demo
That last command is destructive, though its scope is limited to the example directory. Don't adapt it by swapping in a project directory. If you'd rather keep the example, skip it entirely: pdb hasn't changed any of your files, so there's nothing to roll back.
Options that prevent common surprises
The installed pdb3.12 accepts -c or --command to run an initial debugger command, and -m to debug a module rather than a file. To stop at the first line of a module:
pdb3.12 -m package.module
For a quick non-interactive run that continues until an exception fires:
pdb3.12 -c continue calculate.py
Be aware that the command-line debugger reads .pdbrc files from your home directory and the current directory before it runs anything supplied with -c. Those files can set breakpoints, run commands, or continue execution on their own. If a session starts somewhere unexpected, check both files before trusting what it does. Never put secrets in a .pdbrc; debugger expressions can print them straight back out.
The module option isn't interchangeable with a filesystem path: use -m package.module for an importable module, and a filename for a script. Arguments after the target become the debugged program's own arguments, so quote values with spaces the normal shell way.
Done means
- Session started.
pdb3.12 your-script.pyreaches a visible(Pdb)prompt. - Breakpoint hit. Execution stops at the source line you intended.
- State checked.
porppconfirms the values in the current frame. - Stack traced.
where,upanddownlocate the relevant call stack. - Session closed cleanly. You leave with
continueorquit, and remove any temporary breakpoint line from project code.