# Python Debugging

> Python debugging methodology and problem-solving framework. Use when investigating exceptions, async issues, logging problems, or MCP integration failures in Python code.

- Skill: `majiayu000/python-debugging` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add majiayu000/python-debugging`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/python-debugging/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/python-debugging

---


# Python Debugging Methodology

Systematic problem-solving framework for Python code debugging.

## When to Use This Skill

- Investigating exceptions and stack traces
- Debugging asynchronous code (async/await)
- Resolving logging configuration issues
- Troubleshooting MCP server communication errors
- Diagnosing test failures (pytest, unittest)
- Analyzing performance problems

## Debugging Workflow (推論プロセス)

### 1. Interpret Error Messages

**Objective**: Extract actionable information from error output

**Error message anatomy**:
```
Traceback (most recent call last):
  File "script.py", line 42, in main        ← Calling context
    result = process_data(input)            ← Line that triggered error
  File "utils.py", line 15, in process_data ← Where exception was raised
    return data['key']                      ← Failing operation
KeyError: 'key'                             ← Exception type and message
```

**Questions to answer**:
- What is the exception type? (KeyError, AttributeError, TypeError, etc.)
- What was the last successful operation?
- What input triggered the failure?

### 2. Read Stack Traces

**Objective**: Understand execution path to failure point

**Reading strategy**:

**Top-down** (most common):
- Start from outermost frame (entry point)
- Trace execution flow downward
- Understand calling context

**Bottom-up** (for root cause):
- Start from innermost frame (where exception was raised)
- Identify the specific operation that failed
- Trace why inputs were invalid

**Focus points**:
- Function arguments at each level
- Local variables at failure point
- Library code vs application code boundaries

### 3. Reproduce Consistently

**Objective**: Create minimal failing test case

**Simplification steps**:
1. Isolate the failing function
2. Extract minimal input that triggers failure
3. Remove dependencies (use mocks if needed)
4. Create standalone script (~10 lines)

**Example**:
```python
# Minimal reproducer
data = {'wrong_key': 123}
result = data['key']  # KeyError
```

### 4. Form Hypotheses

**Objective**: List plausible causes based on exception type

**Hypothesis by exception type**:

| Exception | Common Causes | Investigation Strategy |
|-----------|---------------|------------------------|
| **KeyError** | Missing dict key, typo | Print dict.keys(), check spelling |
| **AttributeError** | Wrong object type, typo | Print type(obj), check method name |
| **TypeError** | Wrong argument type, arity mismatch | Print type(args), check function signature |
| **ValueError** | Invalid value range, format error | Print actual value, check constraints |
| **IndexError** | List/string index out of bounds | Print len(sequence), check index calculation |
| **ImportError** | Module not installed, wrong path | Check pip list, sys.path |
| **JSONDecodeError** | Malformed JSON, encoding issue | Print raw string, validate JSON syntax |

**For asynchronous code**:
- Is await missing? (RuntimeWarning: coroutine never awaited)
- Is blocking I/O used in async function?
- Are multiple tasks accessing shared state?

**For MCP/RPC issues**:
- Is JSON-RPC request malformed?
- Is server process running?
- Are method names spelled correctly?
- Is timeout too short?

### 5. Select Debugging Strategy

**Objective**: Choose appropriate tool for the problem

**Decision tree**:

```
Is the error location obvious from stack trace?
├─ Yes → Use print() or logging
│        Fast iteration, minimal setup
└─ No → Use pdb debugger
         Interactive inspection needed

Is the problem in async code?
├─ Yes → Use logging with timestamps
│        await points are not debugger-friendly
└─ No → pdb works well

Is it a test failure?
├─ Yes → pytest --pdb
│        Drops into debugger at failure point
└─ No → Standard debugging approach
```

### 6. Apply Debugging Tool

**Objective**: Gather evidence to confirm/reject hypothesis

**Tool selection**:

**print() / logging**: Quick hypothesis testing
```python
print(f"DEBUG: variable={variable}, type={type(variable)}")
```

**pdb**: Interactive investigation
```python
import pdb; pdb.set_trace()  # Breakpoint here
```

**logging**: Persistent diagnostics
```python
logger.debug(f"Processing {count} items")
logger.warning(f"Unexpected value: {value}")
```

## Debugging Strategy Selection

### When to Use print()

**Use for**:
- Quick value inspection
- Hypothesis: "If I print X here, I'll see Y"
- Short-lived debugging sessions

**Limitations**:
- Clutters code
- Must re-run for each new hypothesis
- No interactive inspection

### When to Use pdb

**Use for**:
- Multiple hypotheses to test
- Need to inspect many variables
- Control flow is unclear
- Complex data structures

**Basic commands**:
```
(Pdb) l          # List code around current line
(Pdb) n          # Next line (step over)
(Pdb) s          # Step into function
(Pdb) c          # Continue to next breakpoint
(Pdb) p variable # Print variable value
(Pdb) pp obj     # Pretty-print object
(Pdb) w          # Where am I? (stack trace)
```

### When to Use logging

**Use for**:
- Production code debugging
- Long-running processes
- Intermittent failures
- Timeline reconstruction

**Configuration**:
```python
import logging
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
```

## Error Pattern Recognition

### Common Python Patterns

**Pattern: "NoneType has no attribute X"**
```python
# Cause: Function returned None instead of expected object
result = get_data()  # Returns None on error
result.process()     # AttributeError: 'NoneType' has no attribute 'process'

# Investigation: Why did get_data() return None?
```

**Pattern: "Dictionary changed size during iteration"**
```python
# Cause: Modifying dict while iterating
for key in data:
    if condition:
        del data[key]  # RuntimeError

# Fix: Iterate over copy
for key in list(data.keys()):
    if condition:
        del data[key]
```

**Pattern: "JSON decode error: Expecting value"**
```python
# Cause: Empty or non-JSON response
response = requests.get(url)
data = response.json()  # JSONDecodeError if response is HTML or empty

# Investigation: Print response.text to see actual content
```

### Async/Await Patterns

**Pattern: "Coroutine was never awaited"**
```python
# Wrong: Calling async function without await
result = async_function()  # Returns coroutine object, doesn't execute

# Right: Await the coroutine
result = await async_function()
```

**Pattern: "Event loop already running"**
```python
# Cause: Nested asyncio.run() calls
async def outer():
    asyncio.run(inner())  # Error: loop already running

# Fix: Just await
async def outer():
    await inner()
```

## Hypothesis Testing Techniques

### Binary Search Debugging

Narrow down the failing region:

```python
# Add checkpoints to partition code
def process_data(data):
    print("CHECKPOINT 1: Input valid")  # Check 1
    
    intermediate = transform(data)
    print("CHECKPOINT 2: Transform OK")  # Check 2
    
    result = finalize(intermediate)
    print("CHECKPOINT 3: Finalize OK")  # Check 3
    
    return result

# Run: Which checkpoint is NOT printed? Problem is before it.
```

### Differential Analysis

Compare working vs failing cases:

```python
# Working case
process_data({'key': 'value'})  # ✓ Success

# Failing case  
process_data({'wrong_key': 'value'})  # ✗ KeyError

# Insight: Code assumes 'key' always exists
# Fix: Add default or validation
```

### Isolation Testing

Test components independently:

```python
# Hypothesis: Function X works correctly in isolation
def test_function_x():
    input_data = {'key': 'value'}
    result = function_x(input_data)
    assert result == expected_value

# If test passes: Problem is in calling context or inputs
# If test fails: Problem is in function_x implementation
```

## Logging Strategies

### Log Level Guidelines

| Level | When to Use | Example |
|-------|-------------|---------|
| **DEBUG** | Detailed diagnosis, temporary | `logger.debug(f"Processing item {i}/{total}")` |
| **INFO** | Important events, milestones | `logger.info("MCP server started on port 8000")` |
| **WARNING** | Recoverable issues, deprecations | `logger.warning("Timeout, retrying...")` |
| **ERROR** | Errors that need attention | `logger.error("Failed to connect to DSIM")` |
| **CRITICAL** | System failure, requires immediate action | `logger.critical("License server unreachable")` |

### Structured Logging

Include context in log messages:

```python
# Good: Context-rich logging
logger.info(f"Test {test_name} compilation completed in {duration}s")

# Bad: Ambiguous logging  
logger.info("Compilation completed")
```

### Async Code Logging

Log await points to trace execution:

```python
async def process_request(request_id):
    logger.debug(f"[{request_id}] Starting")
    
    data = await fetch_data()
    logger.debug(f"[{request_id}] Data fetched")
    
    result = await process(data)
    logger.debug(f"[{request_id}] Processing complete")
    
    return result
```

## MCP Server Debugging

### Common MCP Issues

**Pattern: "Method not found"**
```python
# Cause: Typo in method name or not registered
# Investigation: Check server tool registry, verify spelling
```

**Pattern: "Connection refused"**
```python
# Cause: Server not running or wrong port
# Investigation: Check server process, verify port number
```

**Pattern: "Timeout waiting for response"**
```python
# Cause: Server hung, processing too slow, timeout too short
# Investigation: Check server logs, increase timeout, add progress logging
```

### MCP Debugging Workflow

1. **Verify server is running**
   ```python
   # Check process list for MCP server
   # Check server startup logs
   ```

2. **Check request format**
   ```python
   logger.debug(f"Sending request: {json.dumps(request, indent=2)}")
   ```

3. **Monitor server response**
   ```python
   logger.debug(f"Received response: {json.dumps(response, indent=2)}")
   ```

4. **Validate JSON-RPC structure**
   - Is "jsonrpc": "2.0" present?
   - Is "method" spelled correctly?
   - Are "params" in correct format?

## Test Debugging

### pytest Integration

**Run test with debugger**:
```bash
pytest test_file.py::test_function --pdb
# Drops into pdb on first failure
```

**Add breakpoint in test**:
```python
def test_processing():
    data = load_test_data()
    import pdb; pdb.set_trace()  # Inspect here
    result = process(data)
    assert result == expected
```

### Debugging Test Isolation

**Problem**: Test passes alone, fails in suite

**Investigation**:
- Check for shared state (module-level variables)
- Check for fixture side effects
- Run tests in different orders to isolate interaction

## Common Pitfalls

### Don't Ignore Warnings

```python
# RuntimeWarning: coroutine 'func' was never awaited
# ↑ This WILL cause bugs, fix immediately
```

### Don't Assume Exception Type

```python
# Wrong: Catching generic exception
try:
    data = json.loads(text)
except:  # Catches everything, including KeyboardInterrupt
    pass

# Right: Catch specific exception
try:
    data = json.loads(text)
except json.JSONDecodeError as e:
    logger.error(f"Invalid JSON: {e}")
```

### Don't Debug in Production

```python
# Wrong: Leaving debug code in production
import pdb; pdb.set_trace()  # Will hang production server

# Right: Use logging with appropriate level
logger.debug("Debug information")  # Only in dev mode
```

## Integration with Other Skills

- **mcp-workflow**: Use when debugging MCP server integration issues
- **dsim-debugging**: Coordinate when Python MCP server controls DSIM simulator
- **uvm-verification**: Debug UVM test invocation from Python scripts

## Summary

Python debugging is systematic problem-solving:

1. **Interpret** error messages for exception type and context
2. **Read** stack traces to understand execution path
3. **Reproduce** with minimal test case
4. **Form** hypotheses based on exception patterns
5. **Select** appropriate debugging tool (print/pdb/logging)
6. **Test** hypotheses systematically

Key principle: **Understand the error, then investigate the cause**. Don't guess—use evidence from stack traces, logs, and debugger inspection.

