pyguides

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

ParameterTypeDefaultDescription
objectanyThe object to print
streamfile-likeNoneOutput stream (defaults to stdout)
indentint1Spaces per indentation level
widthint80Maximum characters per line
depthintNoneMaximum nesting depth (None = unlimited)
compactboolFalsePack items on fewer lines when True
sort_dictsboolFalseSort dictionary keys alphabetically
underscore_numbersboolFalseAdd 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

ParameterTypeDefaultDescription
objectanyThe 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

ParameterTypeDefaultDescription
objectanyThe 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

ParameterTypeDefaultDescription
objectanyThe 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:

ParameterTypeDefaultDescription
indentint1Number of spaces for each indentation level
widthint80Maximum characters per line before wrapping
depthintNoneMaximum nesting depth to display
compactboolFalsePack items on fewer lines when True
sort_dictsboolvariesSort 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'}}

See Also