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 whererepr=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
Prettyclass inrich/pretty.pyinitializes with configurable styling parameters at lines 53-89. traverse()andpretty_repr()(lines 778-874) construct aNodetree that mirrors object structure while detecting expandable types viais_expandable().- Special handling exists for dataclasses, attrs objects, named tuples, and custom
__rich_repr__implementations. - A
visited_idsset (lines 217-227) prevents infinite recursion on circular references. __rich_console__(lines 104-138) converts the tree into aTextobject with optionalReprHighlightersyntax 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →