Skip to main content

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 debugpy for 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_trace in your environment to make breakpoint() 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 line
  • s (step) - Step into functions
  • c (continue) - Continue execution
  • u (up) - Move up the stack
  • d (down) - Move down the stack

Code Inspection

  • l (list) - Show current code
  • ll (longlist) - Show full function
  • w (where) - Show stack trace
  • pp 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

CommandDescription
hHelp
nNext line
sStep into
cContinue
lList code
llList full function
p <var>Print variable
pp <var>Pretty print
wWhere (stack trace)
uUp stack frame
dDown stack frame
bList/set breakpoints
clClear breakpoints
qQuit 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

  1. Run the script:

    python calculator.py
  2. Inspect variables (when IPDB starts):

    ipdb> p price
    100
    ipdb> p discount_percent
    15
    ipdb> p customer_type
    'premium'
  3. Step through execution:

    ipdb> n  # Next line
    ipdb> p base_discount
    15.0
  4. Use enhanced features:

    ipdb> customer_type?  # Get type info
    ipdb> %whos # List all variables
  5. Test different values:

    ipdb> test_discount = price * 0.2  # Test calculation
    ipdb> pp test_discount
    20.0
  6. 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

  1. Time function execution:

    ipdb> %timeit item ** 2 + random.random()
    # Shows timing statistics
  2. Profile memory usage:

    ipdb> %memit result.append(processed)
    # Memory usage per line (if memory_profiler installed)
  3. Examine variables by type:

    ipdb> %whos list
    # Show only list variables
    ipdb> %whos int
    # Show only integer variables
  4. Check current directory and files:

    ipdb> %pwd
    ipdb> %ls *.py
  5. Run external commands:

    ipdb> !ls -la
    ipdb> !ps aux | grep python
  6. 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

  1. Detailed object inspection:

    ipdb> processor?
    # Basic object info
    ipdb> processor??
    # Full source code
  2. Explore object attributes:

    ipdb> dir(processor)
    # All attributes and methods
    ipdb> [attr for attr in dir(processor) if not attr.startswith('_')]
    # Public attributes only
  3. 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'>}
  4. 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")
  5. 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

  1. 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
  2. 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()
  3. 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)
  4. 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

  1. Explore class hierarchy:

    ipdb> type(self).__mro__
    # Method Resolution Order
    ipdb> isinstance(self, BaseProcessor)
    True
    ipdb> hasattr(self, 'config')
    True
  2. Inspect method signatures:

    ipdb> import inspect
    ipdb> inspect.signature(self.process)
    # Method signature
    ipdb> inspect.getfullargspec(self.process)
    # Full argument specification
  3. 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")
  4. 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
  5. 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 %whos with 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

  1. Go to File > Settings > Tools > External Tools
  2. Click + to add new tool:
    • Name: IPDB Debug
    • Program: python
    • Arguments: $FilePath$
    • Working directory: $ProjectFileDir$
    • Environment variables: PYTHONBREAKPOINT=ipdb.set_trace

Run Configuration

  1. Run > Edit Configurations
  2. 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

Further Reading