IPDB
IPDB is a powerful, interactive debugger for Python that brings the best of IPython to the classic PDB experience. By combining the reliability of Python's built-in debugger with IPython's advanced features—like syntax highlighting, tab completion, and magic commands—IPDB makes debugging more productive and less frustrating. It is designed for developers who want a modern, user-friendly debugging workflow without sacrificing compatibility with standard PDB commands.
Whether you're troubleshooting complex bugs, exploring unfamiliar code, or teaching Python debugging, IPDB offers a seamless, enhanced interface that works in scripts, interactive sessions, and Jupyter notebooks. Its integration with the IPython ecosystem means you get a familiar, feature-rich environment for inspecting variables, stepping through code, and experimenting live.
Overview
IPDB (IPython Debugger) is a drop-in replacement for Python's standard PDB debugger. It provides:
- Syntax highlighting for better code readability
- Tab completion for commands, variables, and file paths
- Access to IPython magic commands within debugging sessions
- Improved object introspection and pretty-printing
- Full compatibility with all standard PDB commands and workflows
IPDB can be invoked programmatically, from the command line, or as the default debugger using the PYTHONBREAKPOINT environment variable.
Example Usage
Install IPDB:
pip install ipdb
Set a breakpoint in your code:
import ipdb
ipdb.set_trace() # Execution will pause here
Or use as the default debugger:
breakpoint() # If PYTHONBREAKPOINT=ipdb.set_trace
Key Features
- Enhanced Interface: Syntax highlighting, tab completion, and magic commands for a modern debugging experience
- Seamless IPython Integration: Use IPython features and extensions directly in your debugging session
- Better Object Inspection: Pretty-printing and advanced introspection for complex data structures
- Flexible Invocation: Start IPDB from code, the command line, or as the default Python debugger
- Jupyter Support: Debug interactively within Jupyter notebooks and IPython shells
- IDE Integration: Works with popular editors like VS Code and PyCharm
- Script and REPL Friendly: Equally useful in scripts, interactive shells, and notebooks
When to Use IPDB
Use IPDB when you need:
- Interactive, step-by-step debugging in Python scripts or notebooks
- Enhanced command-line debugging with tab completion and syntax highlighting
- Deep inspection of complex objects and data structures
- Integration with the IPython/Jupyter ecosystem
- A more productive alternative to standard PDB for daily development
When Not to Use IPDB (or Consider Alternatives)
Consider using standard PDB or other debuggers if:
- You require zero external dependencies (PDB is in the Python standard library)
- You are working in highly restricted or minimal environments (e.g., embedded systems, production containers)
- You need remote debugging or advanced IDE-specific features (consider
debugpyfor VS Code, or PyCharm's debugger) - You want a graphical debugging interface (consider IDE debuggers)
Common Pitfalls
- Missing IPython: IPDB requires IPython; ensure it is installed in your environment
- Non-interactive Environments: Some features may not work in non-interactive shells or CI pipelines
- Conflicting Debuggers: Avoid mixing IPDB with other debuggers in the same session
Best Practices
- Set
PYTHONBREAKPOINT=ipdb.set_tracein your environment to makebreakpoint()use IPDB by default - Use magic commands (e.g.,
%timeit,%pdb) for advanced debugging and performance checks - Integrate IPDB into your test suite for interactive debugging of failing tests
Basics
Learn the fundamentals of IPDB (IPython Debugger) and how it enhances your Python debugging experience.
Installation
IPDB requires installation via pip:
pip install ipdb
For development environments, add to requirements:
# requirements-dev.txt
ipdb>=0.13.0
Starting IPDB
Method 1: Direct Import
import ipdb
ipdb.set_trace() # Execution pauses here
Method 2: Using breakpoint() (Python 3.7+)
Set IPDB as default debugger:
export PYTHONBREAKPOINT=ipdb.set_trace
Then use:
breakpoint() # Uses ipdb instead of pdb
Method 3: Post-mortem Debugging
import ipdb
try:
# your code here
risky_function()
except:
ipdb.post_mortem()
Method 4: Command Line
python -m ipdb script.py
Enhanced Features Over PDB
Syntax Highlighting
IPDB automatically highlights Python syntax in code listings and variable displays:
ipdb> l
1 def calculate_total(items):
2 total = 0
3 -> for item in items:
4 total += item.price * item.quantity
5 return total
Tab Completion
Press Tab to complete:
- Variable names
- Object attributes
- Method names
- File paths
ipdb> my_ob<TAB>
my_object
ipdb> my_object.get_<TAB>
my_object.get_data() my_object.get_name()
Enhanced Object Introspection
IPDB provides better object inspection with IPython's features:
ipdb> my_object?
Type: MyClass
String form: <MyClass object at 0x...>
File: /path/to/file.py
Docstring: MyClass documentation...
ipdb> my_object?? # View source code
Core Navigation Commands
Basic Movement
n(next) - Execute next lines(step) - Step into functionsc(continue) - Continue executionu(up) - Move up the stackd(down) - Move down the stack
Code Inspection
l(list) - Show current codell(longlist) - Show full functionw(where) - Show stack tracepp variable- Pretty print variable
Advanced Navigation
ipdb> n # Next line
ipdb> s # Step into
ipdb> unt 25 # Continue until line 25
ipdb> j 30 # Jump to line 30 (use carefully)
Variable Inspection
Basic Inspection
ipdb> p variable_name # Print variable
ipdb> pp complex_structure # Pretty print
ipdb> pinfo variable_name # Detailed info
IPython Magic Commands
ipdb> %whos # List all variables
ipdb> %whos str # List string variables only
ipdb> %pwd # Current directory
ipdb> %ls # List files
Interactive Exploration
ipdb> my_list = [1, 2, 3, 4, 5]
ipdb> [x*2 for x in my_list if x > 2]
[6, 8, 10]
Breakpoint Management
Setting Breakpoints
ipdb> b # List breakpoints
ipdb> b 15 # Break at line 15
ipdb> b filename.py:20 # Break at line 20 in file
ipdb> b function_name # Break at function entry
Conditional Breakpoints
ipdb> b 25, x > 10 # Break at line 25 when x > 10
ipdb> b function, len(items) == 0 # Break when items is empty
Managing Breakpoints
ipdb> disable 1 # Disable breakpoint 1
ipdb> enable 1 # Enable breakpoint 1
ipdb> cl # Clear all breakpoints
ipdb> cl 1 # Clear breakpoint 1
Expression Evaluation
IPDB allows full Python expression evaluation:
ipdb> total = sum(item.price for item in items)
ipdb> filtered_items = [item for item in items if item.active]
ipdb> import math; math.sqrt(total)
Common Commands Quick Reference
| Command | Description |
|---|---|
h | Help |
n | Next line |
s | Step into |
c | Continue |
l | List code |
ll | List full function |
p <var> | Print variable |
pp <var> | Pretty print |
w | Where (stack trace) |
u | Up stack frame |
d | Down stack frame |
b | List/set breakpoints |
cl | Clear breakpoints |
q | Quit debugger |
Best Practices
1. Use Meaningful Variable Names
ipdb> meaningful_result = complex_calculation(data)
ipdb> pp meaningful_result
2. Combine with IPython Features
ipdb> %timeit expensive_function() # Time function calls
ipdb> %debug # Enhanced debugging
3. Interactive Development
ipdb> # Test different approaches interactively
ipdb> approach1 = process_data_method1(data)
ipdb> approach2 = process_data_method2(data)
ipdb> compare_results(approach1, approach2)
4. Save Debugging Sessions
ipdb> %save debugging_session.py 1-10 # Save commands 1-10
Common Pitfalls
1. Variable Name Conflicts
If variable names conflict with IPDB commands, use !:
ipdb> !n = 5 # Set variable n to 5
ipdb> n # Next line (command)
ipdb> print(!n) # Print variable n
2. Long Output
Use paging for long output:
ipdb> %page large_variable # Page through output
3. Exit Cleanly
Always exit properly:
ipdb> q # Quit debugger
# or
ipdb> c # Continue execution
Configuration
Customize IPDB to match your debugging preferences and workflow requirements.
IPython Configuration Integration
IPDB inherits configuration from IPython, allowing extensive customization through IPython's configuration system.
Create IPython Profile
ipython profile create debug
This creates a profile at ~/.ipython/profile_debug/.
Configuration File Location
Edit ~/.ipython/profile_debug/ipython_config.py:
# IPython configuration for debugging
c = get_config()
# Enhanced tab completion
c.IPCompleter.greedy = True
c.IPCompleter.use_jedi = True
# Better exception handling
c.InteractiveShell.xmode = 'Verbose'
# Auto-reload modules during debugging
c.InteractiveShellApp.extensions = ['autoreload']
c.InteractiveShellApp.exec_lines = ['%autoreload 2']
# Custom prompts
c.TerminalInteractiveShell.prompts_class = 'IPython.terminal.prompts.ClassicPrompts'
IPDB-Specific Configuration
Environment Variables
# Set IPDB as default debugger
export PYTHONBREAKPOINT=ipdb.set_trace
# IPython profile for debugging
export IPYTHON_PROFILE=debug
# Enhanced colors
export IPYTHON_COLORS=Linux
Runtime Configuration
Add to your debugging session:
import ipdb
import IPython
# Configure IPython for current session
IPython.get_ipython().magic('config IPCompleter.greedy=True')
IPython.get_ipython().magic('colors Linux')
Color Schemes
Built-in Color Schemes
# In IPDB session
ipdb> %colors Linux # Green/red scheme
ipdb> %colors LightBG # Light background
ipdb> %colors NoColor # No colors
ipdb> %colors Neutral # Neutral colors
Custom Color Configuration
In ipython_config.py:
# Custom highlighting colors
c.TerminalInteractiveShell.highlighting_style = 'monokai'
c.TerminalInteractiveShell.true_color = True
# Syntax highlighting in debugger
c.TerminalIPythonApp.display_banner = False
Aliases and Shortcuts
Built-in Aliases
IPDB inherits useful IPython aliases:
# In IPDB session
ipdb> %alias # List all aliases
ipdb> %alias ll ls -l # Create new alias
ipdb> %rehashx # Reload system aliases
Custom Debugging Aliases
In ipython_config.py:
c.AliasManager.user_aliases = [
('inspect', 'pinfo %s'),
('source', 'pinfo2 %s'),
('vars', 'whos'),
('trace', 'debug'),
('reload', 'autoreload 2')
]
Persistent Aliases
Create ~/.ipython/profile_debug/startup/00-aliases.py:
# Custom debugging aliases
ip = get_ipython()
# Quick inspection aliases
ip.define_magic('vars', lambda line: ip.magic('whos'))
ip.define_magic('src', lambda line: ip.magic(f'pinfo2 {line}'))
# Navigation shortcuts
ip.define_magic('up5', lambda line: [ip.magic('u') for _ in range(5)])
ip.define_magic('down5', lambda line: [ip.magic('d') for _ in range(5)])
Enhanced Object Inspection
Configuration for Better Introspection
# In ipython_config.py
c.PlainTextFormatter.pprint = True
c.PlainTextFormatter.verbose = True
# Enhanced object info
c.InspectColors.info_border = 'red'
c.InspectColors.info_header = 'yellow'
Custom Inspection Functions
Create ~/.ipython/profile_debug/startup/01-inspection.py:
def inspect_all(obj):
"""Enhanced object inspection"""
print(f"Type: {type(obj)}")
print(f"Dir: {[attr for attr in dir(obj) if not attr.startswith('_')]}")
if hasattr(obj, '__dict__'):
print(f"Attributes: {obj.__dict__}")
if hasattr(obj, '__doc__') and obj.__doc__:
print(f"Docstring: {obj.__doc__[:100]}...")
# Make available in debugging sessions
ip = get_ipython()
ip.user_ns['inspect_all'] = inspect_all
Auto-loading Extensions
Useful Extensions for Debugging
In ipython_config.py:
c.InteractiveShellApp.extensions = [
'autoreload', # Auto-reload modules
'line_profiler', # Performance profiling
'memory_profiler', # Memory usage analysis
]
c.InteractiveShellApp.exec_lines = [
'%autoreload 2',
'import numpy as np',
'import pandas as pd',
'from pprint import pprint as pp',
]
Custom Extension
Create ~/.ipython/extensions/debug_helpers.py:
def load_ipython_extension(ipython):
"""Load debugging helper functions"""
def debug_info():
"""Show current debugging context"""
import sys
frame = sys._getframe(1)
print(f"File: {frame.f_code.co_filename}")
print(f"Function: {frame.f_code.co_name}")
print(f"Line: {frame.f_lineno}")
print(f"Locals: {list(frame.f_locals.keys())}")
ipython.user_ns['debug_info'] = debug_info
Load in config:
c.InteractiveShellApp.extensions.append('debug_helpers')
Startup Scripts
Auto-import Common Modules
Create ~/.ipython/profile_debug/startup/02-imports.py:
# Common debugging imports
import sys
import os
import json
import traceback
from pprint import pprint as pp
from collections import defaultdict, Counter
# Make available globally
ip = get_ipython()
ip.user_ns.update({
'sys': sys,
'os': os,
'json': json,
'traceback': traceback,
'pp': pp,
'defaultdict': defaultdict,
'Counter': Counter,
})
print("Debugging imports loaded: sys, os, json, traceback, pp, defaultdict, Counter")
Debugging Utilities
Create ~/.ipython/profile_debug/startup/03-utils.py:
def quick_vars(frame_locals):
"""Show variables by type"""
by_type = defaultdict(list)
for name, value in frame_locals.items():
if not name.startswith('_'):
by_type[type(value).__name__].append(name)
for type_name, vars in by_type.items():
print(f"{type_name}: {', '.join(vars)}")
def stack_info():
"""Show call stack information"""
import traceback
stack = traceback.extract_stack()
for frame in stack[-5:-1]: # Last 4 frames
print(f"{frame.filename}:{frame.lineno} in {frame.name}")
# Make available in debugging sessions
ip = get_ipython()
ip.user_ns.update({
'quick_vars': quick_vars,
'stack_info': stack_info,
})
History Configuration
Enhanced History Settings
# In ipython_config.py
c.HistoryManager.hist_file = '~/.ipython/profile_debug/history.sqlite'
c.HistoryAccessor.hist_file = '~/.ipython/profile_debug/history.sqlite'
# Save more history
c.HistoryManager.db_cache_size = 1000
History Search Enhancement
# Custom history search
def debug_history(pattern=None):
"""Search debugging history"""
ip = get_ipython()
if pattern:
matches = [line for line in ip.history_manager.input_hist_parsed
if pattern in line]
for i, line in enumerate(matches[-10:], 1):
print(f"{i}: {line}")
else:
# Show last 10 commands
for i, line in enumerate(ip.history_manager.input_hist_parsed[-10:], 1):
print(f"{i}: {line}")
ip.user_ns['debug_history'] = debug_history
Example Complete Configuration
Complete ipython_config.py
c = get_config()
# Tab completion
c.IPCompleter.greedy = True
c.IPCompleter.use_jedi = True
# Colors and display
c.TerminalInteractiveShell.highlighting_style = 'monokai'
c.TerminalInteractiveShell.true_color = True
c.PlainTextFormatter.pprint = True
# Exception handling
c.InteractiveShell.xmode = 'Verbose'
# Extensions and auto-imports
c.InteractiveShellApp.extensions = ['autoreload']
c.InteractiveShellApp.exec_lines = [
'%autoreload 2',
'print("IPDB debugging profile loaded")'
]
# History
c.HistoryManager.db_cache_size = 1000
# Prompts
c.TerminalInteractiveShell.prompts_class = 'IPython.terminal.prompts.ClassicPrompts'
# Aliases
c.AliasManager.user_aliases = [
('inspect', 'pinfo %s'),
('source', 'pinfo2 %s'),
('vars', 'whos'),
]
Using the Configuration
Activate Profile
# Set environment variable
export IPYTHON_PROFILE=debug
# Or start IPDB with profile
python -c "
import ipdb
from IPython import start_ipython
start_ipython(argv=['--profile=debug'])
ipdb.set_trace()
"
Verify Configuration
ipdb> %config
# Shows current configuration
ipdb> %alias
# Shows available aliases
ipdb> debug_info()
# Custom function if configured
Tutorials
Hands-on tutorials to master IPDB debugging techniques with practical examples.
Tutorial 1: Getting Started with IPDB
Scenario: Debugging a Simple Function
Create a file calculator.py:
def calculate_discount(price, discount_percent, customer_type):
"""Calculate discount with customer type bonus"""
import ipdb; ipdb.set_trace() # Debug point
base_discount = price * (discount_percent / 100)
if customer_type == 'premium':
bonus_discount = base_discount * 0.1
elif customer_type == 'vip':
bonus_discount = base_discount * 0.2
else:
bonus_discount = 0
total_discount = base_discount + bonus_discount
final_price = price - total_discount
return final_price
if __name__ == "__main__":
result = calculate_discount(100, 15, 'premium')
print(f"Final price: {result}")
Step-by-step Debugging
-
Run the script:
python calculator.py -
Inspect variables (when IPDB starts):
ipdb> p price
100
ipdb> p discount_percent
15
ipdb> p customer_type
'premium' -
Step through execution:
ipdb> n # Next line
ipdb> p base_discount
15.0 -
Use enhanced features:
ipdb> customer_type? # Get type info
ipdb> %whos # List all variables -
Test different values:
ipdb> test_discount = price * 0.2 # Test calculation
ipdb> pp test_discount
20.0 -
Continue execution:
ipdb> c # Continue to end
Tutorial 2: Using IPython Magic Commands
Scenario: Analyzing Performance Issues
Create performance_test.py:
import time
import random
def slow_function(data):
"""Intentionally slow function for debugging"""
import ipdb; ipdb.set_trace()
result = []
for item in data:
# Simulate processing time
time.sleep(0.001)
processed = item ** 2 + random.random()
result.append(processed)
return result
def main():
test_data = list(range(100))
result = slow_function(test_data)
return result
if __name__ == "__main__":
main()
Performance Analysis with Magic Commands
-
Time function execution:
ipdb> %timeit item ** 2 + random.random()
# Shows timing statistics -
Profile memory usage:
ipdb> %memit result.append(processed)
# Memory usage per line (if memory_profiler installed) -
Examine variables by type:
ipdb> %whos list
# Show only list variables
ipdb> %whos int
# Show only integer variables -
Check current directory and files:
ipdb> %pwd
ipdb> %ls *.py -
Run external commands:
ipdb> !ls -la
ipdb> !ps aux | grep python -
Save debugging session:
ipdb> %save debug_session.py 1-10
# Saves commands 1-10 to file
Tutorial 3: Advanced Variable Inspection
Scenario: Complex Data Structure Debugging
Create data_processor.py:
class DataProcessor:
def __init__(self, name):
self.name = name
self.data = {}
self.processed = False
def add_data(self, key, values):
self.data[key] = values
def process_data(self):
import ipdb; ipdb.set_trace()
processed_data = {}
for key, values in self.data.items():
if isinstance(values, list):
processed_data[key] = {
'sum': sum(values),
'avg': sum(values) / len(values),
'count': len(values),
'max': max(values),
'min': min(values)
}
self.processed_data = processed_data
self.processed = True
return processed_data
def main():
processor = DataProcessor("test_processor")
processor.add_data("sales", [100, 200, 150, 300, 250])
processor.add_data("costs", [50, 80, 60, 120, 90])
processor.add_data("invalid", "not_a_list") # Bug!
result = processor.process_data()
return result
if __name__ == "__main__":
main()
Advanced Inspection Techniques
-
Detailed object inspection:
ipdb> processor?
# Basic object info
ipdb> processor??
# Full source code -
Explore object attributes:
ipdb> dir(processor)
# All attributes and methods
ipdb> [attr for attr in dir(processor) if not attr.startswith('_')]
# Public attributes only -
Interactive data exploration:
ipdb> self.data.keys()
dict_keys(['sales', 'costs', 'invalid'])
ipdb> {k: type(v) for k, v in self.data.items()}
{'sales': <class 'list'>, 'costs': <class 'list'>, 'invalid': <class 'str'>} -
Test fixes interactively:
ipdb> # Test the fix before applying
ipdb> test_item = self.data['invalid']
ipdb> isinstance(test_item, list)
False
ipdb> # Fix: Add type checking
ipdb> if isinstance(values, list):
ipdb> print("Processing list")
ipdb> else:
ipdb> print(f"Skipping {key}: not a list") -
Use IPython's enhanced pprint:
ipdb> from pprint import pprint as pp
ipdb> pp(self.data, width=40)
# Pretty printed with custom width
Tutorial 4: Interactive Debugging Workflow
Scenario: API Integration Debug
Create api_client.py:
import json
import requests
from typing import Dict, List, Optional
class APIClient:
def __init__(self, base_url: str, api_key: str):
self.base_url = base_url
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({'Authorization': f'Bearer {api_key}'})
def fetch_user_data(self, user_ids: List[int]) -> Dict:
import ipdb; ipdb.set_trace()
results = {}
for user_id in user_ids:
url = f"{self.base_url}/users/{user_id}"
try:
response = self.session.get(url)
response.raise_for_status()
results[user_id] = response.json()
except requests.RequestException as e:
results[user_id] = {'error': str(e)}
return results
def main():
# Mock API for testing
client = APIClient("https://jsonplaceholder.typicode.com", "test_key")
user_ids = [1, 2, 999] # 999 might not exist
data = client.fetch_user_data(user_ids)
return data
if __name__ == "__main__":
main()
Interactive Workflow Techniques
-
Modify behavior during debugging:
ipdb> # Test API call interactively
ipdb> test_url = f"{self.base_url}/users/1"
ipdb> test_response = self.session.get(test_url)
ipdb> test_response.status_code
200 -
Mock problematic calls:
ipdb> # Mock a failing response
ipdb> class MockResponse:
ipdb> def __init__(self, status_code, json_data):
ipdb> self.status_code = status_code
ipdb> self._json = json_data
ipdb> def json(self): return self._json
ipdb> def raise_for_status(self): pass
ipdb>
ipdb> response = MockResponse(200, {'id': 999, 'name': 'Test'})
ipdb> results[user_id] = response.json() -
Test error conditions:
ipdb> # Simulate different error types
ipdb> from requests.exceptions import ConnectionError, Timeout
ipdb>
ipdb> try:
ipdb> raise ConnectionError("Network error")
ipdb> except requests.RequestException as e:
ipdb> error_result = {'error': str(e)}
ipdb> pp(error_result) -
Dynamic code modification:
ipdb> # Add logging during debugging
ipdb> import logging
ipdb> logging.basicConfig(level=logging.DEBUG)
ipdb> logger = logging.getLogger(__name__)
ipdb>
ipdb> logger.debug(f"Fetching user {user_id}")
ipdb> # Now continue with logging enabled
Tutorial 5: Debugging with Complex Objects
Scenario: Class Hierarchy Debug
Create inheritance_debug.py:
from abc import ABC, abstractmethod
from typing import Any, Dict
class BaseProcessor(ABC):
def __init__(self, name: str):
self.name = name
self.config = {}
@abstractmethod
def process(self, data: Any) -> Any:
pass
def configure(self, **kwargs):
self.config.update(kwargs)
class TextProcessor(BaseProcessor):
def process(self, data: str) -> Dict[str, Any]:
import ipdb; ipdb.set_trace()
words = data.split()
result = {
'word_count': len(words),
'char_count': len(data),
'uppercase_words': [w for w in words if w.isupper()],
'average_word_length': sum(len(w) for w in words) / len(words) if words else 0
}
if self.config.get('include_lines'):
result['line_count'] = len(data.split('\n'))
return result
class NumberProcessor(BaseProcessor):
def process(self, data: List[float]) -> Dict[str, float]:
import ipdb; ipdb.set_trace()
if not data:
return {'error': 'Empty data'}
result = {
'sum': sum(data),
'mean': sum(data) / len(data),
'max': max(data),
'min': min(data)
}
if self.config.get('include_variance'):
mean = result['mean']
variance = sum((x - mean) ** 2 for x in data) / len(data)
result['variance'] = variance
result['std_dev'] = variance ** 0.5
return result
def main():
text_proc = TextProcessor("text_analyzer")
text_proc.configure(include_lines=True)
number_proc = NumberProcessor("number_analyzer")
number_proc.configure(include_variance=True)
text_result = text_proc.process("Hello WORLD\nThis is a TEST")
number_result = number_proc.process([1, 2, 3, 4, 5])
return text_result, number_result
if __name__ == "__main__":
main()
Advanced Object Debugging
-
Explore class hierarchy:
ipdb> type(self).__mro__
# Method Resolution Order
ipdb> isinstance(self, BaseProcessor)
True
ipdb> hasattr(self, 'config')
True -
Inspect method signatures:
ipdb> import inspect
ipdb> inspect.signature(self.process)
# Method signature
ipdb> inspect.getfullargspec(self.process)
# Full argument specification -
Dynamic method testing:
ipdb> # Test method with different inputs
ipdb> test_inputs = ["short", "a much longer sentence with multiple words"]
ipdb> for test_input in test_inputs:
ipdb> test_result = test_input.split()
ipdb> print(f"Input: {test_input[:20]}... -> {len(test_result)} words") -
Configuration debugging:
ipdb> pp(self.config)
{'include_lines': True}
ipdb> # Test configuration changes
ipdb> original_config = self.config.copy()
ipdb> self.config['debug_mode'] = True
ipdb> # Restore if needed
ipdb> self.config = original_config -
Memory and performance analysis:
ipdb> import sys
ipdb> sys.getsizeof(data)
# Memory usage of data
ipdb> %timeit data.split()
# Time the operation
Best Practices Summary
1. Interactive Development
- Test fixes before applying them
- Use magic commands for analysis
- Save useful debugging sessions
2. Efficient Inspection
- Use
?and??for quick object info - Leverage tab completion extensively
- Combine
%whoswith type filtering
3. Complex Debugging
- Break down complex operations into steps
- Test edge cases interactively
- Use mock objects for external dependencies
4. Configuration Management
- Set up custom profiles for different projects
- Create reusable debugging utilities
- Use startup scripts for common imports
Integration
Integrate IPDB into your development workflow with IDEs, frameworks, and CI/CD pipelines.
Setting IPDB as Default Debugger
System-wide Configuration
Add to your shell profile (.bashrc, .zshrc, etc.):
# Set IPDB as default debugger
export PYTHONBREAKPOINT=ipdb.set_trace
# Optional: Set IPython profile
export IPYTHON_PROFILE=debug
Project-specific Configuration
Add to your project's .env file:
PYTHONBREAKPOINT=ipdb.set_trace
IPYTHON_PROFILE=debug
Virtual Environment Setup
# In your virtual environment activation script
echo "export PYTHONBREAKPOINT=ipdb.set_trace" >> venv/bin/activate
IDE Integration
VS Code Integration
Launch Configuration
Create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: IPDB Debug",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"env": {
"PYTHONBREAKPOINT": "ipdb.set_trace"
},
"justMyCode": false
},
{
"name": "Python: IPDB with Arguments",
"type": "python",
"request": "launch",
"program": "${file}",
"args": ["--debug", "--verbose"],
"console": "integratedTerminal",
"env": {
"PYTHONBREAKPOINT": "ipdb.set_trace",
"IPYTHON_PROFILE": "debug"
}
}
]
}
VS Code Settings
Add to .vscode/settings.json:
{
"python.terminal.activateEnvironment": true,
"python.defaultInterpreterPath": "./venv/bin/python",
"terminal.integrated.env.linux": {
"PYTHONBREAKPOINT": "ipdb.set_trace"
},
"terminal.integrated.env.osx": {
"PYTHONBREAKPOINT": "ipdb.set_trace"
},
"terminal.integrated.env.windows": {
"PYTHONBREAKPOINT": "ipdb.set_trace"
}
}
VS Code Tasks
Create .vscode/tasks.json for quick debugging:
{
"version": "2.0.0",
"tasks": [
{
"label": "Run with IPDB",
"type": "shell",
"command": "python",
"args": ["${file}"],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "new"
},
"options": {
"env": {
"PYTHONBREAKPOINT": "ipdb.set_trace"
}
}
}
]
}
PyCharm Integration
External Tool Configuration
- Go to File > Settings > Tools > External Tools
- Click + to add new tool:
- Name: IPDB Debug
- Program:
python - Arguments:
$FilePath$ - Working directory:
$ProjectFileDir$ - Environment variables:
PYTHONBREAKPOINT=ipdb.set_trace
Run Configuration
- Run > Edit Configurations
- Add new Python configuration:
- Name: IPDB Debug
- Script path: Your script
- Environment variables:
PYTHONBREAKPOINT=ipdb.set_trace
Framework Integration
Python Testing with IPDB
Basic Testing Setup
# Set up IPDB for tests
import os
os.environ['PYTHONBREAKPOINT'] = 'ipdb.set_trace'
def test_with_ipdb():
"""Test with IPDB debugging"""
data = {'key': 'value', 'numbers': [1, 2, 3]}
# Use IPDB's enhanced features
breakpoint() # Will use IPDB
# In IPDB, use tab completion and magic commands
# ipdb> data.keys() # Tab completion works
# ipdb> %pinfo data # Get detailed info
assert data['key'] == 'value'
Testing Integration
pytest Integration
conftest.py Configuration
# conftest.py
import os
import pytest
def pytest_configure(config):
"""Configure pytest for IPDB debugging"""
if config.getoption("--ipdb"):
os.environ['PYTHONBREAKPOINT'] = 'ipdb.set_trace'
def pytest_addoption(parser):
parser.addoption(
"--ipdb", action="store_true",
help="Use IPDB for debugging"
)
@pytest.fixture
def debug_mode():
"""Fixture to enable debugging in specific tests"""
original = os.environ.get('PYTHONBREAKPOINT')
os.environ['PYTHONBREAKPOINT'] = 'ipdb.set_trace'
yield
if original:
os.environ['PYTHONBREAKPOINT'] = original
else:
os.environ.pop('PYTHONBREAKPOINT', None)
Test Examples
# test_example.py
import pytest
import ipdb
def test_with_debug(debug_mode):
"""Test that uses debug fixture"""
data = [1, 2, 3, 4, 5]
breakpoint() # Will use IPDB due to fixture
result = sum(data)
assert result == 15
def test_manual_debug():
"""Test with manual IPDB"""
ipdb.set_trace()
# Test debugging
# ipdb> data = [1, 2, 3]
# ipdb> sum(data)
assert True
@pytest.mark.debug
def test_debug_marker():
"""Test marked for debugging"""
if pytest.current_pytest_config.getoption("--ipdb"):
ipdb.set_trace()
assert 1 + 1 == 2
Running Tests with IPDB
# Run with IPDB option
pytest --ipdb tests/
# Run specific test with debugging
pytest --ipdb -k test_with_debug
# Run with capture disabled (better for debugging)
pytest --ipdb -s tests/
unittest Integration
Test Case with IPDB
# test_unittest.py
import unittest
import os
import ipdb
class TestWithIPDB(unittest.TestCase):
@classmethod
def setUpClass(cls):
if os.getenv('DEBUG_TESTS'):
os.environ['PYTHONBREAKPOINT'] = 'ipdb.set_trace'
def test_debug_example(self):
"""Test with debugging capability"""
data = {'key': 'value'}
if os.getenv('DEBUG_TESTS'):
ipdb.set_trace()
# Debug test state
# ipdb> self.assertEqual(data['key'], 'value')
# ipdb> data['new_key'] = 'new_value'
self.assertEqual(data['key'], 'value')
def debug_helper(self, data):
"""Helper method for debugging"""
ipdb.set_trace()
# Debug helper context
# ipdb> type(data)
# ipdb> len(data) if hasattr(data, '__len__') else 'No length'
return data
if __name__ == '__main__':
# Enable debugging for this run
os.environ['DEBUG_TESTS'] = '1'
unittest.main()
Jupyter Notebook Integration
IPython Magic Commands
# In Jupyter cell
%load_ext autoreload
%autoreload 2
# Set IPDB as default
import os
os.environ['PYTHONBREAKPOINT'] = 'ipdb.set_trace'
# Import and use
import ipdb
ipdb.set_trace()
Notebook Debugging Cell
%%debug
# This cell will start debugging mode
def problematic_function():
data = [1, 2, 3]
result = sum(data) / 0 # Division by zero
return result
problematic_function()
CI/CD Considerations
Conditional Debugging
# utils/debug.py
import os
def conditional_debug():
"""Only debug in development environment"""
if os.getenv('ENVIRONMENT') == 'development':
import ipdb
ipdb.set_trace()
def safe_debug():
"""Safe debugging that won't break CI"""
try:
if os.getenv('ALLOW_DEBUG', '').lower() == 'true':
import ipdb
ipdb.set_trace()
except ImportError:
pass # IPDB not available, continue
Docker Integration
Dockerfile with IPDB
FROM python:3.9
# Install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# Install IPDB for development
RUN pip install ipdb
# Development environment
ENV PYTHONBREAKPOINT=ipdb.set_trace
ENV ENVIRONMENT=development
COPY . /app
WORKDIR /app
CMD ["python", "main.py"]
Docker Compose for Development
# docker-compose.dev.yml
version: '3.8'
services:
app:
build: .
environment:
- PYTHONBREAKPOINT=ipdb.set_trace
- ENVIRONMENT=development
stdin_open: true # Required for IPDB input
tty: true # Required for IPDB terminal
volumes:
- .:/app
ports:
- "8000:8000"
Running with Docker
# Run with interactive mode for debugging
docker-compose -f docker-compose.dev.yml run --rm app python script.py
# Or for web applications
docker-compose -f docker-compose.dev.yml up
# When breakpoint hits, attach to container:
docker exec -it container_name bash
Best Practices
1. Environment Management
- Use environment variables for debugging control
- Create separate configurations for development/production
- Document debugging setup in project README
2. IDE Integration
- Configure launch configurations for common debugging scenarios
- Set up keyboard shortcuts for quick debugging
- Use workspace-specific settings
3. Framework Integration
- Respect framework conventions
- Use middleware/hooks for framework-specific debugging
- Test debugging setup with framework-specific tools
4. Team Collaboration
- Share debugging configurations via version control
- Document common debugging workflows
- Use consistent environment variable names