Readable Python trees for nested data
Using Python's pprint to display nested data structures in a tree format makes configuration files, API payloads, and build reports easier to scan. The standard library supplies dependable formatting, but pprint produces indented representations rather than literal branches. A small recursive helper adds the visual hierarchy while retaining pprint for leaf values.
That distinction matters during debugging. A dictionary can contain several services, each with environment settings and health checks, and a flat representation can hide where one branch ends. Clear output also suits a developer portfolio: it explains technical behaviour without requiring readers to reconstruct the hierarchy themselves.
Treat pprint as the formatting engine, not the whole visual design. The approach below uses plain indentation, so it works in terminals, logs, documentation, and continuous integration output on common Linux systems. Stable key order makes two runs of the same payload easy to compare.
What pprint actually produces
pprint.pformat() returns a formatted Python representation as a string, while pprint.pprint() prints it directly. Both support indentation, line width, dictionary ordering, and recursion depth.
from pprint import pformat
data = {
"site": "example.au",
"build": {"python": "3.12", "tests": 42},
}
print(pformat(data, indent=2, width=80, sort_dicts=False))
The result is readable, but its braces still define the hierarchy implicitly. This is often enough for a short dictionary or debugging log. It becomes harder to follow when mappings contain lists of mappings, particularly when several items share similar keys. A branch renderer makes each parent-child relationship visible at a glance.
Building a branch-oriented tree
To create literal branches, traverse the value recursively and use pprint only to render terminal values. This version accepts mappings and sequences, labels their children, and prints directly to standard output.
from collections.abc import Mapping, Sequence
from pprint import pformat
def show_tree(value, label="root", level=0):
pad = " " * level
if isinstance(value, Mapping):
print(f"{pad}{label}:")
for key, child in value.items():
show_tree(child, str(key), level + 1)
elif isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
print(f"{pad}{label} [{len(value)}]")
for index, child in enumerate(value):
show_tree(child, f"[{index}]", level + 1)
else:
rendered = pformat(value, width=72, sort_dicts=False)
print(f"{pad}{label}: {rendered}")
The mapping branch prints a parent label, then descends into each key-value pair. The sequence branch adds its length and visits items by index. Strings, bytes, and byte arrays are deliberately excluded from the sequence branch so they remain readable leaves rather than becoming a character-by-character tree.
For a leaf, pformat handles quotes, escapes, long values, and objects with a sensible repr. The surrounding indentation supplies the hierarchy; the formatter supplies familiar scalar syntax.
Supporting common container types
The use of Mapping and Sequence covers ordinary dictionaries, lists, tuples, and compatible third-party containers. Sets are displayed as leaves because their order has no useful positional meaning. If the application uses dataclasses, convert them with dataclasses.asdict() before rendering, or add a dedicated branch that exposes selected fields.
Input order is preserved by the helper, whereas plain pprint sorts dictionary keys by default. Pass sort_dicts=False to both when sequence order carries meaning. Also distinguish an empty container from a missing key: the current renderer shows both as a label, so add an explicit marker such as {} or [] when that distinction matters in operational logs.
Choosing useful display settings
These options control the formatted leaf and any nested representation pformat handles:
indent=2adds two spaces per nesting level.width=88wraps long values near the terminal edge.sort_dicts=Falseretains insertion order.compact=Truecan keep short sequences together.
A consistent tree also benefits from a few display conventions:
- Show each mapping key exactly once.
- Put list lengths beside parent labels.
- Escape embedded newlines in leaf values.
- Keep one branch per line for easy grepping.
These are presentation choices, not data transformations. Keeping them inside the renderer prevents display rules from leaking into application logic and makes command-line output, unit-test fixtures, and generated documentation more consistent.
Applying trees to Australian workflows
Australian teams often work across very different environments. A Sydney software team and a Melbourne operations group may combine cloud deployment records with logistics data from interstate services. Billing exports commonly use Australian dollars and include 10% GST, so keeping the original numeric values in the tree avoids accidental currency conversion.
Operational reports often use day-first dates such as 27/03/2026 and label eastern-state times as AEST or AEDT. For a realistic parsing exercise, save the HTML returned by a public page such as Military Police Vietnam and feed a simplified metadata dictionary into the renderer. The example also reinforces why untrusted text should be parsed, never evaluated.
Local open-source communities add another useful context. Presentations and meetups associated with PyCon AU and Linux.conf.au often generate configuration samples, issue payloads, and command output that maintainers want to inspect. Australian English also uses “licence” for a software licence, while SPDX metadata retains the formal License identifier, a small distinction worth preserving in project documentation.
Keeping output maintainable
Treat the renderer as presentation code and test it with representative fixtures: nested dictionaries, empty containers, long strings, Unicode, and custom objects. A recursive function can loop forever on circular data, so add an identity-based guard when input is not fully trusted. A depth ceiling can also stop malformed payloads from flooding a terminal.
Keeping traversal separate from data access makes the result easier to reuse. The helper can write to standard output, a logging handler, or a list of lines without fetching or modifying the source data. That separation is valuable for tools such as DenyHosts, Kodos, and Scratchy, where users need to understand what the program found and why.
This lightweight approach fits Phil Schwartz’s open-source portfolio: the hierarchy stays visible, the dependency list stays small, and pprint remains the standard-library formatting component beneath the tree.
