IceCream
A Python debugging library that prints both an expression and its value, so you never have to write print("x:", x) again.
Repository Health
Technical Analysis
IceCream is a Python debugging library built around a single function, ic(), that inspects its own call site to print both the expression you passed and its evaluated value — turning a debug print statement like print(foo(123)) into a one-character call that self-documents. It uses the executing library to reliably locate its own call in the source AST (even across multi-line calls, method chains, or when several ic() calls appear on one line), formats output with syntax highlighting via Pygments, and pretty-prints nested data structures for readability.
Beyond variable inspection, calling ic() with no arguments prints the calling filename, line number, and parent function to trace execution flow — a lightweight replacement for scattering print(1)/print(2) breadcrumbs through conditional branches. Output can be redirected into a logging pipeline, disabled globally in production via ic.disable(), or installed onto Python’s builtins module so no import is required across an entire project.
What You Get
- Self-documenting output -
ic(x)prints both the expression text and its value, so you never needprint("x:", x)boilerplate. - Syntax-highlighted, pretty-printed values - Output is colorized via Pygments and formatted with a tuned
pprint.pformat()wrapper for readable nested data structures. - Execution tracing - Calling
ic()with no arguments prints the filename, line number, and parent function, useful for tracing branchy control flow. - Global install/disable -
install()addsicto Python’s builtins so it’s available in every file without imports;ic.disable()/enable()toggles output globally, e.g. for production.
Common Use Cases
- Quick variable inspection while iterating on a function - drop
ic(result)inline instead of writing a labeledprint()call, then delete it when done. - Tracing which branch of an if/else executed - call bare
ic()inside each branch to see exactly which path and line ran without adding custom print markers. - Debugging inside REPL-unfriendly code paths -
ic.format()returns the formatted string so it can be piped intologging.debug()instead of stderr. - Project-wide debug helper without per-file imports - call
install()once at startup soic()is available as a builtin across the whole codebase during development.
Under The Hood
Architecture
The library is a small, flat module set: icecream.py holds the single IceCreamDebugger class that is the entire public surface, coloring.py defines a standalone Pygments SolarizedDark style, and builtins.py patches Python’s builtins module to install/uninstall the global ic name. Rather than subclassing, configuration is done through injectable callables (prefix, outputFunction, argToStringFunction) set via configureOutput(), a strategy-style design that keeps the default zero-config path simple while still being pluggable. The core trick the whole library depends on is Source.executing(callFrame) from the third-party executing package, which walks the call frame to recover the exact AST node of the ic() call so its source text can be reprinted alongside its value; when that lookup fails (a REPL, a frozen executable, or source that changed at runtime) the code falls back to a Sentinel.absent marker and emits a RuntimeWarning rather than crashing, so the degradation path was designed in rather than bolted on. Timer, exposed via the ic.timer property, reuses the same IceCreamDebugger instance as both a decorator and a context manager for measuring elapsed time.
Tech Stack
A pure-Python 3.8–3.14 (plus PyPy3.10) library with no runtime framework dependencies beyond colorama (Windows ANSI passthrough), pygments (lexing/highlighting), and executing/asttokens (frame-to-AST resolution). It ships as a setuptools-built wheel with an inline py.typed marker, and its GitHub Actions CI runs a tox matrix across every supported Python version plus a dedicated mypy job configured with disallow_untyped_defs/disallow_untyped_calls in pyproject.toml.
Code Quality
A roughly 1,000-line unittest.TestCase suite in tests/test_icecream.py exercises install()/uninstall(), context tracing, coloring, the timer, format(), and enable/disable behavior, including regex assertions on ANSI escape sequences in output. Source files are extensively type-annotated (cast, Literal, Union, generics) and checked by a strict mypy configuration in CI, and error handling is explicit — safe_pformat() catches TypeError from pprint, retries with adjusted arguments, and warns before falling back to repr(). Naming conventions mix camelCase (formatPair, includeContext) with snake_case (has_non_ascii_chars), likely reflecting the project’s age and later contributions, and no dedicated linter step (e.g. ruff/flake8) runs in CI alongside mypy.
API Design
ic(x) is deliberately the same shape as print(x), so adopting it requires no new syntax to learn — the self-documenting output is the entire pitch. configureOutput() provides escape hatches (custom prefix, output routing to logging, per-type serializers via argumentToString.register()) without complicating the zero-config default. Reusing the same IceCreamDebugger object as both a decorator and a context manager for timing, and offering install() to make ic() available as a language builtin, are both small but genuinely ergonomic touches that reduce everyday debugging friction.