# How Rich Implements Python Pretty-Printing with the Pretty Class

> Discover how Rich implements Python pretty-printing with the Pretty class. Learn about the three-layer pipeline that transforms objects into formatted, styled Text for clearer debugging.

- Repository: [Textualize/rich](https://github.com/Textualize/rich)
- Tags: internals
- Published: 2026-03-06

---

**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`](https://github.com/Textualize/rich/blob/main/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:

```python
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))

```

```python
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

```

```python
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`](https://github.com/Textualize/rich/blob/main/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`](https://github.com/Textualize/rich/blob/main/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.