Python Debugging — Trivia & Interesting Facts¶
Surprising, historical, and little-known facts about Python debugging.
pdb was included in Python 1.0 in 1994¶
The Python Debugger (pdb) shipped with the very first production release of Python. It was modeled after gdb's command interface, with commands like break, continue, step, next, and print. Over 30 years later, pdb remains the standard debugger, though its interface has improved significantly — Python 3.7 added the breakpoint() built-in function to replace the awkward import pdb; pdb.set_trace() idiom.
Python's traceback module was revolutionary for readability¶
When Python launched, most languages displayed cryptic error messages or numeric error codes. Python's traceback — showing the full call stack with file names, line numbers, and the actual source code of each frame — was unusually friendly for its era. The format has been so successful that it influenced error reporting in Ruby, JavaScript, and other dynamic languages.
The PYTHONFAULTHANDLER environment variable catches segfaults¶
Setting PYTHONFAULTHANDLER=1 (or calling faulthandler.enable()) makes Python dump a traceback when the interpreter crashes from a segfault, bus error, or abort signal. Without it, a segfault in a C extension produces no Python-level diagnostic at all. This feature, added in Python 3.3, has saved countless hours debugging crashes in numpy, pandas, and other C-extension-heavy codebases.
Python 3.11 made tracebacks dramatically more precise¶
Python 3.11 (October 2022) introduced "fine-grained error locations" — tracebacks now point to the exact expression within a line that caused the error using caret markers. Instead of just showing the line number, the traceback underlines the specific operation that failed, like which attribute access in a chained expression raised an AttributeError.
sys.settrace() is the hook behind all Python debuggers¶
Every Python debugger — pdb, PyCharm's debugger, VS Code's debugger — uses sys.settrace() to register a callback that the interpreter calls before executing each line, function call, return, and exception. This tracing mechanism imposes a 2-10x performance overhead, which is why running under a debugger is noticeably slower than normal execution.
The dis module lets you read Python bytecode like assembly language¶
python -m dis script.py (or dis.dis(function)) disassembles Python functions into their bytecode instructions. This reveals exactly what the interpreter does: LOAD_FAST, BINARY_ADD, CALL_FUNCTION, etc. Understanding bytecode is essential for diagnosing performance issues where the same Python expression compiles to surprisingly different instruction counts.
print debugging has a fancy name: printf debugging¶
The technique of inserting print statements to trace program execution is called "printf debugging" (after C's printf function). While debugger purists dismiss it, a 2017 survey found that print debugging remains the most commonly used debugging technique across all experience levels. Python 3.8's f-string = specifier (f"{var=}") was added specifically to make print debugging faster — it prints both the variable name and its value.
Python's -i flag drops you into an interactive shell after a crash¶
Running python -i script.py executes the script normally, but if it raises an unhandled exception, instead of exiting, Python drops into an interactive REPL with all variables still in scope. Combined with import pdb; pdb.pm() (post-mortem debugger), you can inspect the exact state of the program at the moment of failure.
Memory profiling in Python is hard because of reference counting¶
Python uses reference counting (with a generational garbage collector for cycles) rather than pure tracing GC. This means objects are freed immediately when their reference count hits zero, making memory profiles non-deterministic across runs. Tools like tracemalloc (standard library since 3.4), objgraph, and memory_profiler each take different approaches, and none gives a complete picture without the others.
The cgitb module provides HTML-formatted tracebacks¶
The cgitb module (originally designed for CGI web scripts) produces rich, colorful tracebacks showing local variable values at each frame. While rarely used in modern web development (superseded by frameworks' own error pages), it remains useful as a quick way to get more context than the default traceback. Enable it with cgitb.enable(format='text') for terminal output.