Loading...
Development

Add debugging with pdb

Below is the new section you can insert right after Section 14.5 – Testing with pytest (or before the Mini-Project).
It follows the same clean, professional style and includes real-world debugging workflows using pdb and ipdb.


14.6 Debugging with pdb (and ipdb)

“Debugging is twice as hard as writing the code in the first place.”Brian Kernighan


Why Debug?

  • print() debugging → fragile, clutters code
  • pdbinteractive, step-by-step control
  • Works in scripts, functions, Jupyter, and even post-mortem

1. Install ipdb (Enhanced pdb)

pip install ipdb

ipdb = pdb + IPython features:

  • Syntax highlighting
  • Tab completion
  • Better ! shell access

Add to requirements.txt:

ipdb>=0.13

2. Basic pdb Usage

Method 1: Insert breakpoint in code

# src/buggy.py
def calculate_tax(income: float) -> float:
    import pdb; pdb.set_trace()  # ← BREAKPOINT
    rate = 0.2
    if income > 100_000:
        rate = 0.3
    tax = income * rate
    return round(tax, 2)

print(calculate_tax(120_000))

Run:

python src/buggy.py

You’ll see:

> /path/to/buggy.py(4)calculate_tax()
-> rate = 0.2
(Pdb)

3. Essential pdb Commands

CommandShortcutAction
listlShow current code
nextnStep over (next line)
stepsStep into function
continuecRun until next breakpoint
returnrRun until function returns
print(var)p varPrint variable
pp dictPretty-print
wherewShow stack trace
up / downMove up/down call stack
interactStart local Python REPL
quitqExit debugger

Example Session:

(Pdb) l
  1     def calculate_tax(income: float) -> float:
  2         import pdb; pdb.set_trace()
  3         rate = 0.2
  4  ->     if income > 100_000:
  5             rate = 0.3
  6         tax = income * rate
  7         return round(tax, 2)

(Pdb) p income
120000.0
(Pdb) n
> ...line 5...
(Pdb) p rate
0.3
(Pdb) c  # continue

4. Modern Alternative: breakpoint() (Python 3.7+)

No need to import pdb!

def risky_operation(x: int):
    result = x / (x - 5)
    breakpoint()  # ← Auto uses ipdb if installed
    return result * 2

print(risky_operation(3))

Python automatically uses:

  • ipdb → if installed
  • pdb → fallback

5. Post-Mortem Debugging (After Crash)

# src/crash.py
def divide(a, b):
    return a / b

divide(10, 0)  # ZeroDivisionError

Run with post-mortem:

python -m pdb src/crash.py

At crash:

ZeroDivisionError: division by zero
Uncaught exception. Entering post mortem debugging
> ...crash.py(2)divide()
-> return a / b
(Pdb) p a, b
(10, 0)
(Pdb) where
...

Or automatically:

python -m pdb -c continue src/crash.py

6. Conditional Breakpoints

def process_items(items):
    for i, item in enumerate(items):
        if i == 3:  # only stop at index 3
            breakpoint()
        print(f"Processing {item}")

Or in pdb:

(Pdb) break calculate_tax, income > 150000
(Pdb) c

7. Debugging in VS Code

  1. Install Python extension
  2. Set breakpoint (click left gutter)
  3. Press F5 → Select "Python File"
  4. Debug panel: watch variables, call stack, breakpoints

launch.json example:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug with ipdb",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "env": {"PYTHONBREAKPOINT": "ipdb.set_trace"}
        }
    ]
}

Set PYTHONBREAKPOINT=ipdb.set_tracebreakpoint() uses ipdb


8. Real-World Example: Debug the Task Manager

# task_manager.py (add this line to debug)
def add(self, title: str):
    if not title.strip():
        raise ValueError("Title cannot be empty")
    self.tasks.append(Task(title))
    breakpoint()  # ← STOP HERE
    self.save()

Run:

python task_manager.py add "  "

You’ll hit:

(Pdb) p title
'  '
(Pdb) p title.strip()
''
(Pdb) up
> ...main()...
(Pdb) p sys.argv
['task_manager.py', 'add', '  ']

Now you see the bug: empty/whitespace titles!


9. Best Practices

DoDon’t
Use breakpoint() in Python ≥3.7Use print() for complex logic
Remove pdb.set_trace() before commitCommit pdb lines
Use conditional breakpointsStop on every loop iteration
Combine with logging for productionRely only on debugger
Use ipdb in devUse raw pdb if ipdb is available

10. Quick Reference

# Run with auto post-mortem
python -m pdb -c continue script.py

# Force ipdb
PYTHONBREAKPOINT=ipdb.set_trace python script.py

# In code
breakpoint()  # best
# or
import ipdb; ipdb.set_trace()

Bonus: .pdbrc – Custom Startup

Create ~/.pdbrc:

# ~/.pdbrc
alias pp import pprint; pprint.pprint(%1)
alias vars p self.__dict__ if hasattr(self, '__dict__') else locals()

Now in debugger:

(Pdb) pp my_dict
(Pdb) vars

Summary

ToolUse Case
breakpoint()Modern, clean, auto-uses ipdb
ipdbRich REPL, tab completion
pdbAlways available, lightweight
VS CodeGUI debugging, breakpoints, watch
Post-mortemDebug crashes instantly

Now you can find and fix bugs like a pro!

Next Step: Add a deliberate bug in the Task Manager, then use breakpoint() to fix it interactively.


Insert this section into your notes after Section 14.5.
Your Python tutorial is now complete with testing + debugging.


Happy Debugging!