The 30-Line Debug Print I Keep in My Dotfiles

A pretty-printer for Python objects that I paste into every new project. Shows types, depth, and truncates long containers, so I stop reaching for `pprint` mid-incident.

Python
Compiler
3 snippets
debugging
py-decorators
utility
code-template
owentoure

By @owentoure

February 14, 2026

·

Updated August 12, 2026

995 views

4

4.5 (13)

from __future__ import annotations
import sys
from typing import Any

_MAX_ITEMS = 5
_MAX_STR = 80

def dprint(value: Any, label: str = '', depth: int = 0, _seen: set | None = None) -> None:
    if _seen is None:
        _seen = set()
    pad = '  ' * depth
    head = f'{pad}{label + ": " if label else ""}'
    obj_id = id(value)
    if obj_id in _seen and isinstance(value, (dict, list, tuple, set)):
        print(f'{head}<cycle {type(value).__name__}>', file=sys.stderr)
        return
    _seen.add(obj_id)
    if isinstance(value, dict):
        print(f'{head}dict({len(value)})', file=sys.stderr)
        for i, (k, v) in enumerate(value.items()):
            if i >= _MAX_ITEMS:
                print(f'{pad}  ...{len(value) - _MAX_ITEMS} more', file=sys.stderr)
                break
            dprint(v, repr(k), depth + 1, _seen)
    elif isinstance(value, (list, tuple, set)):
        print(f'{head}{type(value).__name__}({len(value)})', file=sys.stderr)
        for i, v in enumerate(list(value)[:_MAX_ITEMS]):
            dprint(v, f'[{i}]', depth + 1, _seen)
        if len(value) > _MAX_ITEMS:
            print(f'{pad}  ...{len(value) - _MAX_ITEMS} more', file=sys.stderr)
    elif isinstance(value, str):
        s = value if len(value) <= _MAX_STR else value[:_MAX_STR] + f'...({len(value)})'
        print(f'{head}str {s!r}', file=sys.stderr)
    else:
        print(f'{head}{type(value).__name__} {value!r}', file=sys.stderr)

if __name__ == '__main__':
    dprint({'user': {'id': 7, 'tags': ['admin', 'beta', 'eu', 'mfa', 'gdpr', 'soc2']}}, 'payload')

This is the debug-print I have been pasting into Python projects since around 2019. It prints to stderr so it never mixes with structured stdout, recurses one level at a time with two-space indentation, and bails out on cycles via an id() set. The two constants at the top, _MAX_ITEMS and _MAX_STR, are the part that matters most during real incidents: when a payload is 50k items deep, pprint will lock your terminal, but dprint shows the first five and a trailing count. I leave the file at ~/.local/lib/python/dprint.py and import sys; sys.path.insert(0, ...) it from a breakpoint() shell.