The pprint module formats complex data structures for human readability. Essential for debugging, logging, and understanding nested data.

Basic Pretty Printing

from pprint import pprint
 
data = {
    'users': [
        {'name': 'Alice', 'age': 30, 'roles': ['admin', 'editor']},
        {'name': 'Bob', 'age': 25, 'roles': ['viewer']},
    ],
    'metadata': {'version': '1.0', 'created': '2024-01-01'}
}
 
# Standard print (hard to read)
print(data)
# {'users': [{'name': 'Alice', 'age': 30, 'roles': ['admin', 'editor']}, ...
 
# Pretty print (readable)
pprint(data)
# {'metadata': {'created': '2024-01-01', 'version': '1.0'},
#  'users': [{'age': 30, 'name': 'Alice', 'roles': ['admin', 'editor']},
#            {'age': 25, 'name': 'Bob', 'roles': ['viewer']}]}

Controlling Width

from pprint import pprint
 
data = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
 
# Default width (80)
pprint(data)
# {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
 
# Narrow width (forces wrapping)
pprint(data, width=40)
# {'key1': 'value1',
#  'key2': 'value2',
#  'key3': 'value3'}
 
# Very narrow
pprint(data, width=1)
# {'key1': 'value1',
#  'key2': 'value2',
#  'key3': 'value3'}

Controlling Depth

from pprint import pprint
 
nested = {
    'level1': {
        'level2': {
            'level3': {
                'level4': 'deep value'
            }
        }
    }
}
 
# Show only 2 levels deep
pprint(nested, depth=2)
# {'level1': {'level2': {...}}}
 
# Full depth (default)
pprint(nested)
# {'level1': {'level2': {'level3': {'level4': 'deep value'}}}}

Indentation

from pprint import pprint
 
data = {'a': [1, 2, 3], 'b': [4, 5, 6]}
 
# Default indent (1)
pprint(data, width=20)
# {'a': [1, 2, 3],
#  'b': [4, 5, 6]}
 
# Larger indent
pprint(data, width=20, indent=4)
# {'a': [1, 2, 3],
#     'b': [4, 5, 6]}

Getting String Output

from pprint import pformat
 
data = {'name': 'Alice', 'scores': [95, 87, 92]}
 
# Get formatted string (don't print)
formatted = pformat(data)
print(type(formatted))  # <class 'str'>
 
# Use in logging
import logging
logging.info("Data: %s", pformat(data))
 
# Use in f-strings
message = f"Received:\n{pformat(data)}"

PrettyPrinter Class

Reusable formatter with custom settings:

from pprint import PrettyPrinter
 
# Create configured printer
pp = PrettyPrinter(indent=2, width=60, depth=3)
 
data = {'key': 'value', 'nested': {'a': 1, 'b': 2}}
 
pp.pprint(data)
formatted = pp.pformat(data)

Compact Mode (Python 3.8+)

Fit more items per line:

from pprint import pprint
 
items = list(range(20))
 
# Default (one per line when it doesn't fit)
pprint(items, width=40)
# [0,
#  1,
#  2,
#  ...
 
# Compact (squeeze multiple per line)
pprint(items, width=40, compact=True)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
#  12, 13, 14, 15, 16, 17, 18, 19]

Sorting Keys

from pprint import pprint
 
data = {'z': 1, 'a': 2, 'm': 3}
 
# Default: sorted keys (Python 3.8+)
pprint(data)
# {'a': 2, 'm': 3, 'z': 1}
 
# Preserve insertion order
pprint(data, sort_dicts=False)
# {'z': 1, 'a': 2, 'm': 3}

Debugging with pprint

from pprint import pprint
 
def process_api_response(response):
    # Debug: see what we got
    print("Response received:")
    pprint(response, depth=2)
    
    # Process...
    return response['data']
 
# In debugger or REPL
response = {'data': {'users': [...], 'meta': {...}}}
pprint(response)

Custom Objects

from pprint import pprint
 
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
    
    def __repr__(self):
        return f"User(name={self.name!r}, email={self.email!r})"
 
users = [User('Alice', 'alice@example.com'), User('Bob', 'bob@example.com')]
 
pprint(users)
# [User(name='Alice', email='alice@example.com'),
#  User(name='Bob', email='bob@example.com')]

Logging Integration

from pprint import pformat
import logging
 
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
 
def log_response(response: dict):
    """Log API response in readable format."""
    logger.debug("API Response:\n%s", pformat(response))
 
# Usage
log_response({'status': 'ok', 'data': {'items': [1, 2, 3]}})

File Output

from pprint import pprint
 
data = {'config': {'debug': True, 'options': ['a', 'b', 'c']}}
 
# Write to file
with open('debug_output.txt', 'w') as f:
    pprint(data, stream=f)
 
# Or use pformat
with open('debug_output.txt', 'w') as f:
    f.write(pformat(data))

Comparing with JSON

import json
from pprint import pformat
 
data = {'users': [{'name': 'Alice'}, {'name': 'Bob'}]}
 
# JSON (no trailing commas, double quotes)
print(json.dumps(data, indent=2))
# {
#   "users": [
#     {"name": "Alice"},
#     {"name": "Bob"}
#   ]
# }
 
# pprint (Python syntax, single quotes)
print(pformat(data))
# {'users': [{'name': 'Alice'}, {'name': 'Bob'}]}

Practical Patterns

Config Debugging

from pprint import pformat
 
def load_config(path: str) -> dict:
    config = {'loaded': True, 'settings': {}}  # Load from file
    print(f"Loaded config from {path}:\n{pformat(config)}")
    return config

Test Output

from pprint import pformat
 
def test_api_response():
    response = fetch_api()
    
    assert 'data' in response, f"Missing 'data' in:\n{pformat(response)}"

REPL Helper

# In your Python REPL config (~/.pythonrc.py)
from pprint import pprint as pp
 
# Now use pp() for quick inspection
# >>> pp(some_complex_dict)

When to Use pprint

ScenarioTool
Debug outputpprint()
Log filespformat()
JSON API responsejson.dumps(indent=2)
Machine processingrepr() or json.dumps()
User displayCustom formatting

The pprint module is your debugging companion for complex data. Use it whenever you need to understand what's actually in your nested structures.

React to this post: