How Rich Implements Python Pretty-Printing with the Pretty Class

Rich implements Python object pretty-printing through a three-layer pipeline that converts objects into a node tree via traverse(), renders that tree to a formatted string through Node.render(), and wraps the output in a styled Text object via __rich_console__.

The Textualize/rich library transforms complex Python objects into readable, syntax-highlighted representations through the Pretty class. Unlike standard repr() output, this implementation automatically handles line wrapping, container expansion, and recursive structures while adapting to terminal constraints. Understanding the internal mechanics of Rich pretty-printing reveals how the library balances performance, safety, and visual clarity across terminals and Jupyter notebooks.

The Three-Layer Rendering Pipeline

The Pretty class operates through a distinct architectural separation between object representation, tree construction, and final console output.

Layer 1: Renderable Creation

The process begins in rich/pretty.py where the Pretty class (inheriting from JupyterMixin) initializes at lines 53-89. This constructor accepts the target object alongside styling parameters including indent_size, max_length, max_depth, max_string, and overflow handling preferences. These configuration values persist as instance attributes, controlling how the subsequent tree generation and rendering phases behave.

Layer 2: Tree Construction

The core transformation occurs through pretty_repr() (lines 778-809), which orchestrates the conversion of Python objects into a structured node tree. This method calls traverse() (lines 785-874), a recursive function that examines object types and constructs Node instances representing the object's structure. During this phase, the system identifies expandable containers, detects dataclasses and attrs objects, extracts namedtuple fields, and invokes user-defined __rich_repr__ methods when available.

Layer 3: Console Output

Finally, Pretty.__rich_console__ (lines 104-138) executes the rendering protocol required by Rich's console pipeline. This method obtains the formatted string from the node tree, instantiates a Text object with ANSI styling, applies the optional ReprHighlighter for syntax coloring, and yields the result. If indent_guides is enabled, the method calls Text.with_indent_guides() (lines 131-134) to add visual indentation markers.

Building the Representation Tree

The tree construction phase involves sophisticated type detection and safety mechanisms to handle arbitrary Python objects reliably.

Detecting Expandable Objects

Before traversal, is_expandable() (lines 998-1006) determines whether an object requires recursive decomposition. This function checks if the object is a container (list, dict, set, etc.), a dataclass instance, an attrs object, or implements __rich_repr__, while explicitly excluding class types to prevent infinite descent. Only objects passing this test receive child nodes in the tree.

Special Type Handling

Rich recognizes several specialized formats during tree construction:

  • Dataclasses: Identified via is_dataclass() and _is_dataclass_repr(), iterating only over fields where repr=True.
  • Attrs objects: Detected by _is_attr_object() and processed using _get_attr_fields() to extract declared attributes.
  • Named tuples: Recognized by _is_namedtuple() and _has_default_namedtuple_repr(), utilizing _asdict() to generate key-value pairs for traversal.
  • Custom representations: Objects defining __rich_repr__ (processed around lines 590-640) return iterable argument sequences that the tree builder interprets as constructor-style representations.

Container Structure Mapping

Generic containers map to specific bracket pairs through the _BRACES dictionary consulted at line 627. This mapping associates Python types with their string delimiters—for example, list maps to ["[", "]"] while dict maps to ["{", "}"]—ensuring consistent structural notation in the final output.

Recursion Safety

To prevent infinite loops on self-referencing structures, the traverse() function maintains a visited_ids set (lines 217-227). Before processing any object, the system checks if its id() already exists in this set, skipping traversal if detected. This mechanism safely handles circular references in complex data structures.

Node Rendering Logic

Each Node object stores its textual representation, opening and closing braces, child references, and type flags such as is_tuple or is_namedtuple. The Node.render() method (lines 668-693) walks this tree, expanding multi-line representations only when line length exceeds the console width or when expand_all=True forces full expansion.

Console Integration and Display Hooks

Beyond standalone usage, Rich integrates deeply with Python's interactive environments.

Rich Console Protocol

When Console.print() receives a Pretty instance, the engine invokes __rich_console__, which coordinates between pretty_repr() and the highlighter pipeline. The method constructs the final Text object (lines 116-122) and applies ReprHighlighter by default to colorize brackets, strings, numbers, and keywords according to terminal capabilities.

REPL and Jupyter Integration

The install() function (lines 171-210) enables automatic pretty-printing by replacing sys.displayhook with a wrapper that instantiates Pretty(value) for non-Rich objects. For Jupyter environments, _ipy_display_hook() (lines 113-151) registers with the IPython formatter system, ensuring that notebook cells display formatted representations instead of standard repr() strings.

Practical Usage Examples

The following examples demonstrate common patterns for utilizing Rich's pretty-printing capabilities:

from rich import print
from rich.pretty import Pretty

# Complex nested structure with automatic truncation

data = {"numbers": list(range(20)), "nested": {"a": 1, "b": [2, 3, 4]}}
print(Pretty(data, max_length=5, max_depth=2, indent_guides=True))
from rich.pretty import install

# Enable automatic pretty-printing in REPL

install()

# Subsequent object evaluations display formatted output

my_obj = {"big": "x" * 100, "list": list(range(15))}
my_obj  # Displays truncated, highlighted representation
from rich.highlighter import ReprHighlighter

# Custom syntax highlighting

class MyHighlighter(ReprHighlighter):
    def highlight(self, text):
        return text.stylize("bold red", r"\b\d+\b")

print(Pretty(data, highlighter=MyHighlighter()))

Summary

  • The Pretty class in rich/pretty.py initializes with configurable styling parameters at lines 53-89.
  • traverse() and pretty_repr() (lines 778-874) construct a Node tree that mirrors object structure while detecting expandable types via is_expandable().
  • Special handling exists for dataclasses, attrs objects, named tuples, and custom __rich_repr__ implementations.
  • A visited_ids set (lines 217-227) prevents infinite recursion on circular references.
  • __rich_console__ (lines 104-138) converts the tree into a Text object with optional ReprHighlighter syntax coloring.
  • The install() function (lines 171-210) enables automatic pretty-printing across standard REPL and Jupyter Notebook environments.

Frequently Asked Questions

How does Rich prevent infinite recursion when printing circular references?

The traverse() function in rich/pretty.py maintains a visited_ids set (lines 217-227) that tracks the memory addresses of objects currently being processed. Before traversing any object, the system checks if its id() exists in this set; if present, the algorithm skips that branch, preventing infinite loops while still indicating the circular reference in the output.

What types receive special handling during tree construction?

Rich specifically detects and processes dataclasses (checking repr=True fields), attrs objects (iterating declared attributes), named tuples (using _asdict()), and objects implementing __rich_repr__ (treating returned sequences as constructor arguments). Additionally, standard containers like list, dict, set, and tuple receive bracket mapping through the _BRACES dictionary at line 627.

How can I enable automatic pretty-printing in my Python REPL?

Import install from rich.pretty and invoke install(), which replaces sys.displayhook with a wrapper that automatically wraps evaluation results in Pretty instances. For Jupyter notebooks, this same function registers _ipy_display_hook() (lines 113-151) with the IPython display formatter, ensuring all cell outputs use Rich's formatting instead of the default representation.

What customization options are available for the Pretty output?

The Pretty constructor accepts parameters including max_length (limiting sequence items), max_string (truncating long strings), max_depth (controlling nesting levels), indent_guides (adding visual indentation markers), and highlighter (accepting custom ReprHighlighter subclasses). These options propagate through to pretty_repr() and Node.render(), allowing fine-grained control over the final representation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →