Python Debugger
Debugging Process
- Understand the Error -> 2. Reproduce -> 3. Isolate -> 4. Identify Root Cause -> 5. Fix -> 6. Verify
Step 1: Understand the Error
Reading Tracebacks
Traceback (most recent call last): <- Read bottom to top File "app.py", line 45, in main <- Entry point result = process_data(data) <- Call chain File "processor.py", line 23, in process_data return transform(item) <- Getting closer File "transformer.py", line 12, in transform return item["value"] / item["count"] <- Error location ZeroDivisionError: division by zero <- The actual error
Common error types: see references/python-error-types.md
Step 2: Reproduce the Issue
Create a minimal test case that triggers the error. Answer these questions:
-
What input triggered this?
-
Is it consistent or intermittent?
-
When did it start happening?
-
What changed recently?
Step 3: Isolate the Problem
Print Debugging
def process_data(data): print(f"DEBUG: data type = {type(data)}") print(f"DEBUG: data = {data}")
for i, item in enumerate(data):
print(f"DEBUG: processing item {i}: {item}")
result = transform(item)
print(f"DEBUG: result = {result}")
return results
Using pdb
import pdb
def problematic_function(x): pdb.set_trace() # Execution stops here # Or use: breakpoint() # Python 3.7+ result = x * 2 return result
pdb commands: see references/pdb-commands.md
Using icecream
from icecream import ic
def calculate(x, y): ic(x, y) # Prints: ic| x: 5, y: 0 result = x / y ic(result) return result
Step 4: Common Root Causes
-
None values: Check return values before accessing attributes. Guard with if x is None: raise ValueError(...)
-
Type mismatches: Add type hints, cast inputs explicitly. int(a) + int(b) not a + b
-
Mutable default arguments: Use def f(items=None): then items = items or [] inside
-
Circular imports: Use lazy imports inside functions: from .module import Class
-
Async/await: Missing await returns coroutine instead of result
-
Key/Index errors: Use .get(key, default) for dicts, check len() for lists
-
Scope issues: global /nonlocal declarations, closure variable capture in loops
-
Encoding: Specify encoding="utf-8" in open() calls
-
Float precision: Use decimal.Decimal or math.isclose() for comparisons
-
Resource leaks: Use with statements for files, connections, locks
Step 5: Fix Patterns
Defensive Programming
def safe_divide(a, b): if b == 0: raise ValueError("Cannot divide by zero") return a / b
def safe_get(data: dict, key: str, default=None): return data.get(key, default)
Input Validation
def process_user(user_id: int, data: dict) -> dict: if not isinstance(user_id, int) or user_id <= 0: raise ValueError(f"Invalid user_id: {user_id}")
required_fields = ["name", "email"]
missing = [f for f in required_fields if f not in data]
if missing:
raise ValueError(f"Missing required fields: {missing}")
Exception Handling
import logging
logger = logging.getLogger(name)
def fetch_user_data(user_id: int) -> dict: try: response = api_client.get(f"/users/{user_id}") response.raise_for_status() return response.json() except requests.HTTPError as e: logger.error(f"HTTP error fetching user {user_id}: {e}") raise except requests.ConnectionError: logger.error(f"Connection failed for user {user_id}") raise ServiceUnavailableError("API unavailable")
Step 6: Verify the Fix
import pytest
def test_transform_handles_zero_count(): """Verify fix for ZeroDivisionError.""" data = {"value": 10, "count": 0}
with pytest.raises(ValueError, match="count cannot be zero"):
transform(data)
def test_transform_normal_case(): """Verify normal operation still works.""" data = {"value": 10, "count": 2} result = transform(data) assert result == 5
Debugging Tools
Logging Setup
import logging
logging.basicConfig( level=logging.DEBUG, format="%(asctime)s %(name)s %(levelname)s: %(message)s", handlers=[ logging.FileHandler("debug.log"), logging.StreamHandler(), ], )
Profiling
Time profiling
import cProfile cProfile.run("main()", "output.prof")
Memory profiling
from memory_profiler import profile
@profile def memory_heavy_function(): # ...
Using rich for better output
from rich.traceback import install install(show_locals=True) # Enhanced tracebacks
References
-
Debug Checklist
-
Common Error Types
-
pdb Commands
When to Use WebSearch
-
Cryptic error messages
-
Library-specific errors
-
Version compatibility issues
-
Undocumented behavior