Add debugging with pdb
Below is the new section you can insert right after Section 14 – Project Structure Example (or before the Mini-Project).
Add debugging with pdb
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 codepdb→ interactive, 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
| Command | Shortcut | Action |
|---|---|---|
list |
l |
Show current code |
next |
n |
Step over (next line) |
step |
s |
Step into function |
continue |
c |
Run until next breakpoint |
return |
r |
Run until function returns |
print(var) |
p var |
Print variable |
pp dict |
Pretty-print | |
where |
w |
Show stack trace |
up / down |
Move up/down call stack | |
interact |
Start local Python REPL | |
quit |
q |
Exit 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
- Install Python extension
- Set breakpoint (click left gutter)
- Press
F5→ Select "Python File" - 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_trace→breakpoint()usesipdb
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
| Do | Don’t |
|---|---|
Use breakpoint() in Python ≥3.7 |
Use print() for complex logic |
Remove pdb.set_trace() before commit |
Commit pdb lines |
| Use conditional breakpoints | Stop on every loop iteration |
| Combine with logging for production | Rely only on debugger |
Use ipdb in dev |
Use 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
| Tool | Use Case |
|---|---|
breakpoint() |
Modern, clean, auto-uses ipdb |
ipdb |
Rich REPL, tab completion |
pdb |
Always available, lightweight |
| VS Code | GUI debugging, breakpoints, watch |
| Post-mortem | Debug 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!
Add debugging with pdb
Below is the new section you can insert right after Section 14 – Project Structure Example (or before the Mini-Project).
Add debugging with pdb
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 codepdb→ interactive, 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
| Command | Shortcut | Action |
|---|---|---|
list |
l |
Show current code |
next |
n |
Step over (next line) |
step |
s |
Step into function |
continue |
c |
Run until next breakpoint |
return |
r |
Run until function returns |
print(var) |
p var |
Print variable |
pp dict |
Pretty-print | |
where |
w |
Show stack trace |
up / down |
Move up/down call stack | |
interact |
Start local Python REPL | |
quit |
q |
Exit 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
- Install Python extension
- Set breakpoint (click left gutter)
- Press
F5→ Select "Python File" - 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_trace→breakpoint()usesipdb
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
| Do | Don’t |
|---|---|
Use breakpoint() in Python ≥3.7 |
Use print() for complex logic |
Remove pdb.set_trace() before commit |
Commit pdb lines |
| Use conditional breakpoints | Stop on every loop iteration |
| Combine with logging for production | Rely only on debugger |
Use ipdb in dev |
Use 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
| Tool | Use Case |
|---|---|
breakpoint() |
Modern, clean, auto-uses ipdb |
ipdb |
Rich REPL, tab completion |
pdb |
Always available, lightweight |
| VS Code | GUI debugging, breakpoints, watch |
| Post-mortem | Debug 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!