Debugging and Tooling
|
This section documents the current Python release line as published at the official Python documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Beyond print(), Python ships an interactive debugger and profilers in the standard library, and the
ecosystem has settled on a small set of linters and formatters for keeping code consistent.
Debugging with pdb
breakpoint() and stepping commands
Calling the built-in breakpoint() drops into the Python Debugger (pdb) at that exact line, documented
at the pdb module:
def total(items: list[int]) -> int:
result = 0
for item in items:
breakpoint() # pauses here on the first iteration
result += item
return result
total([1, 2, 3])
Running the script drops into the (Pdb) prompt, where a handful of one-letter commands drive execution:
$ python3 sums.py
> sums.py(5)total()
-> result += item
(Pdb) p item
1
(Pdb) n
> sums.py(3)total()
-> for item in items:
(Pdb) p result
1
(Pdb) c
The core commands are n (next — run the current line, step over calls), s (step — step into a
function call), c (continue — run until the next breakpoint), p <expr> (print an expression), and l
(list — show source around the current line). q quits the debugger entirely.
PYTHONBREAKPOINT=0 python3 sums.py disables every breakpoint() call without editing the source — useful for leaving them in during development and skipping them in CI.
Post-mortem debugging
pdb.pm() inspects the stack of the last uncaught exception, after the fact, from the interactive
interpreter — no need to have set a breakpoint in advance:
$ python3 -i crashy.py
Traceback (most recent call last):
File "crashy.py", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero
>>> import pdb; pdb.pm()
> crashy.py(2)<module>()
-> 1 / 0
(Pdb) l
Running a whole script under the debugger from the start, so it stops automatically at the first
unhandled exception, uses python3 -m pdb:
$ python3 -m pdb crashy.py
> crashy.py(1)<module>()
(Pdb) c
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
Uncaught exception. Entering post mortem debugging
> crashy.py(2)<module>()
-> 1 / 0
(Pdb)
Performance Basics
Micro-benchmarks with timeit
timeit runs a snippet many times and reports the best
timing, avoiding the noise of a single time.time() measurement:
python3 -m timeit -s "data = list(range(1000))" "sorted(data)"
$ python3 -m timeit -s "data = list(range(1000))" "sorted(data)"
20000 loops, best of 5: 12.3 usec per loop
The -s option runs a one-time setup statement (building data) that is excluded from the timing; the
timed statement (sorted(data)) is what gets repeated.
Profiling a script with cProfile
cProfile instruments every function call in a run and
reports where the time actually went:
python3 -m cProfile -s cumulative slow_script.py
$ python3 -m cProfile -s cumulative slow_script.py
2814 function calls in 0.842 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.001 0.001 0.842 0.842 slow_script.py:1(<module>)
1 0.003 0.003 0.841 0.841 slow_script.py:4(main)
1000 0.812 0.001 0.812 0.001 slow_script.py:9(fib)
-s cumulative sorts by cumulative time (a function plus everything it calls), which is usually the
fastest way to spot the actual bottleneck before reaching for timeit on the specific line found.
Linters and Formatters
The ecosystem has no single mandated tool — any of the following is a reasonable choice, and many projects combine a formatter with a linter:
-
ruff — a fast linter (and formatter) that replaces most of what
flake8plus several plugins used to do:pip install ruff ruff check . -
black — an opinionated formatter with almost no configuration; it rewrites code to one consistent style instead of just flagging violations:
pip install black black . -
flake8— the longer-standing linter combiningpyflakes(error detection) andpycodestyle(style checks), documented at flake8.pycqa.org:pip install flake8 flake8 .
Editor Tooling Recap
IDLE, VS Code, and PyCharm were already introduced for writing and running a first script in
Getting Started with Python; all three also drive the debugger described here directly from the
editor — clicking a line number to set a breakpoint instead of typing breakpoint(), and stepping with
buttons that map to pdb’s `n/s/c commands underneath.
See Also
-
Getting Started with Python — installing Python and choosing an editor or IDE.
-
Unit Testing with pytest & Mocking — writing automated tests instead of debugging interactively.
-
Docstrings — the adjacent documentation-tooling ecosystem: docstring styles and generating static documentation sites.