pprint
The pprint module provides functions to display Python data structures in a format that is easy to read. When you print a complex nested structure like a list of dictionaries, the default print() shows everything on one line. The pprint module formats these structures with line breaks and indentation so you can see the structure clearly.
This is useful when debugging, inspecting API responses, or logging complex data.
Functions
pp()
The main function for printing formatted output. Added in Python 3.8.
pp(object, stream=None, indent=1, width=80, depth=None, *, compact=False, sort_dicts=False, underscore_numbers=False)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
object | any | — | The object to print |
stream | file-like | None | Output stream (defaults to stdout) |
indent | int | 1 | Spaces per indentation level |
width | int | 80 | Maximum characters per line |
depth | int | None | Maximum nesting depth (None = unlimited) |
compact | bool | False | Pack items on fewer lines when True |
sort_dicts | bool | False | Sort dictionary keys alphabetically |
underscore_numbers | bool | False | Add underscores as thousand separators |
The following example demonstrates the default output and shows how the depth parameter limits nesting for a quick structural overview without expanding deeply nested containers.
Examples
import pprint
data = {
"users": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
],
"count": 2
}
pprint.pp(data)
# {'count': 2,
# 'users': [{'age': 30, 'name': 'Alice'},
# {'age': 25, 'name': 'Bob'}]}
# Limit depth to see structure at a glance
pprint.pp(data, depth=1)
# {'count': 2, 'users': [modules::]}
The pp() function, added in Python 3.8, preserves dictionary insertion order by default. If you prefer sorted dictionary keys and are working in a codebase that predates Python 3.8, the pprint() function provides the same interface with sorting enabled by default, matching the behavior that earlier Python users would expect.
An alias for pp() with sort_dicts defaulting to True. Use this if you want dictionaries sorted by default.
pprint(object, stream=None, indent=1, width=80, depth=None, *, compact=False, sort_dicts=True, underscore_numbers=False)
The difference between pp() and pprint() becomes clear when you print a dictionary with keys that are not alphabetically ordered. The example below shows how pprint() sorts them while pp() preserves the original insertion order, which is the only behavioral difference between the two functions.
Examples
import pprint
config = {"zebra": 1, "apple": 2, "mango": 3}
# pprint() sorts dict keys by default
pprint.pprint(config)
# {'apple': 2, 'mango': 3, 'zebra': 1}
# pp() preserves insertion order by default
pprint.pp(config)
# {'zebra': 1, 'apple': 2, 'mango': 3}
Both pp() and pprint() write directly to a stream. When you need the formatted output as a string instead — for logging, returning from an API, or embedding in a larger message — the pformat() function returns the formatted string rather than printing it.
Returns the formatted representation as a string instead of printing it.
pformat(object, indent=1, width=80, depth=None, *, compact=False, sort_dicts=True, underscore_numbers=False)
Parameters
Same as pp().
The example below shows the default output for a list of dictionaries and demonstrates how reducing the width to 40 characters forces the formatter to break lines sooner, producing a taller but narrower output that can be easier to scan in a narrow terminal window.
Examples
import pprint
data = [{"id": i, "name": f"user_{i}"} for i in range(3)]
# Get formatted string for logging
log_line = pprint.pformat(data)
print(log_line)
# [{'id': 0, 'name': 'user_0'},
# {'id': 1, 'name': 'user_1'},
# {'id': 2, 'name': 'user_2'}]
# Use with custom width
short = pprint.pformat(data, width=40)
print(short)
# [{'id': 0, 'name': 'user_0'},
# {'id': 1, 'name': 'user_1'},
# {'id': 2, 'name': 'user_2'}]
Formatting output for display is one concern; determining whether that output can be parsed back into an equivalent object is another. The isreadable() function tells you if the formatted representation would survive a round trip through eval(), which matters when you are using pprint to serialize data.
Determines if the formatted representation can be used with eval() to recreate the object. Returns False for recursive structures or objects that cannot be reconstructed.
isreadable(object)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
object | any | — | The object to check |
The function returns True when the formatted output could be passed to eval() to reconstruct an equivalent object, and False when it cannot. The examples below check a simple dictionary and list against a self-referential structure.
Examples
import pprint
# Simple objects are readable
print(pprint.isreadable({"a": 1}))
# True
print(pprint.isreadable([1, 2, 3]))
# True
# Recursive structures are not readable
data = [1, 2]
data.append(data) # Self-referential
print(pprint.isreadable(data))
# False
If an object is not readable, the reason is often that it contains a recursive reference — a data structure that points to itself. The isrecursive() function specifically detects this case without attempting a full readability check.
isrecursive()
Determines if an object requires a recursive representation (contains a reference to itself).
isrecursive(object)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
object | any | — | The object to check |
The function returns a boolean indicating whether the object graph contains a cycle. The examples below show a normal list returning False, a list that appends itself returning True, and a dictionary where a key points back to the containing dictionary also returning True.
Examples
import pprint
# Normal object
print(pprint.isrecursive([1, 2, 3]))
# False
# Self-referential list
data = [1, 2]
data.append(data)
print(pprint.isrecursive(data))
# True
# Recursive dictionary
d = {"key": None}
d["key"] = d
print(pprint.isrecursive(d))
# True
Detecting recursion is useful for choosing a safe serialization strategy, but you also need a way to actually print recursive structures without crashing. The saferepr() function handles this by replacing recursive references with a descriptive placeholder instead of following the cycle indefinitely.
saferepr()
Returns a string representation that handles recursion safely. Instead of causing a RecursionError, it marks recursive references.
saferepr(object)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
object | any | — | The object to represent |
Unlike a regular repr() call that would raise a RecursionError on self-referential structures, saferepr() detects cycles and inserts a placeholder marker instead. The examples below show the safe output for a list that contains itself and a dictionary whose value points back to the dictionary.
Examples
import pprint
# Normal object
print(pprint.saferepr([1, 2, 3]))
# [1, 2, 3]
# Recursive structure - safe!
data = ["hello"]
data.append(data)
print(pprint.saferepr(data))
# [<Recursion on list with id=...>, 'hello']
# Nested recursion
nested = {"self": None}
nested["self"] = nested
print(pprint.saferepr(nested))
# {'self': <Recursion on dict with id=...>}
All the functions covered so far use the module’s default formatting settings each time they are called. When you need to apply the same custom settings repeatedly — for example, a specific indentation width and line length across many log statements — creating a PrettyPrinter instance captures those preferences once and reuses them.
PrettyPrinter Class
For repeated use with the same settings, create a PrettyPrinter instance.
pprint.PrettyPrinter(indent=1, width=80, depth=None, stream=None, *, compact=False, sort_dicts=True, underscore_numbers=False)
Once you have a configured instance, you can call its methods just like the module-level functions but without passing the formatting parameters each time. The example below creates a printer with four-space indentation and a narrower width of 40 characters, then uses it to print a nested data structure.
Examples
import pprint
# Create a configured PrettyPrinter
pp = pprint.PrettyPrinter(indent=4, width=40, sort_dicts=True)
# Use it multiple times efficiently
data = {"players": [{"name": "Alice"}, {"name": "Bob"}]}
pp.pprint(data)
# { 'players': [ { 'name': 'Alice'},
# { 'name': 'Bob'}]}
# Access individual methods
formatted = pp.pformat(data)
print(pp.isreadable(data))
# True
The PrettyPrinter constructor accepts the same formatting parameters as the module-level functions. The table below summarizes the parameters that are common across all pprint interfaces, with their default values and effects.
Common Parameters
These parameters work across most pprint functions:
| Parameter | Type | Default | Description |
|---|---|---|---|
indent | int | 1 | Number of spaces for each indentation level |
width | int | 80 | Maximum characters per line before wrapping |
depth | int | None | Maximum nesting depth to display |
compact | bool | False | Pack items on fewer lines when True |
sort_dicts | bool | varies | Sort dictionary keys alphabetically |
The parameters alone tell you what each knob does, but concrete patterns show how they combine in practice. The following examples demonstrate two common real-world uses: inspecting an API response during debugging and logging structured data with a consistent format.
Common Patterns
Debugging API responses
import pprint
import json
from urllib.request import urlopen
# Fetch and pretty-print JSON data
with urlopen('https://httpbin.org/json') as response:
data = json.load(response)
pprint.pp(data)
# {'slideshow': {'author': 'Yours Truly',
# 'date': 'date of publication',
# 'slides': [{'title': 'Wake up to WonderWidgets!',
# 'type': 'all'},
# {'': 'Why <em>WonderWidgets</em> are great',
# 'type': 'all'},
# {'': '', 'type': 'all'}],
# 'title': 'Sample Slide Show'}}
Printing directly to stdout works well during interactive debugging, but for applications that use a logging framework, you want the formatted output to appear in log files alongside timestamps and severity levels. The pformat() function returns a string, which you can then pass directly to any logging method.
import pprint
import logging
logging.basicConfig(level=logging.DEBUG)
class PrettyLogger:
def log(self, msg, data):
formatted = pprint.pformat(data, width=60)
logging.debug(f"{msg}:\n{formatted}")
logger = PrettyLogger()
logger.log("Request payload", {"headers": {"auth": "token"}, "body": {"query": "test"}})
# DEBUG:root:Request payload:
# DEBUG:root:{'body': {'query': 'test'},
# 'headers': {'auth': 'token'}}