IceCream is a print debugging library that prints expression names alongside values. pip install icecream. Basic: from icecream import ic; ic(variable) → prints ic| variable: value. No return: ic() → prints filename and line. Pass-through: y = ic(expensive_fn(x)) — prints result AND assigns. Multi-arg: ic(a, b, c). Inline: result = func(ic(arg)). Context: from icecream import ic; ic.includeContext = True — shows file:line. Prefix: ic.prefix = "debug| ". Format: ic.configureOutput(prefix=">> ", includeContext=True). Disable: ic.disable(). Re-enable: ic.enable(). Install globally: ic.install() — makes ic a builtin. Uninstall: ic.uninstall(). stderr: default output to stderr. Redirect: ic.configureOutput(outputFunction=print). To string: from icecream import ic; ic.configureOutput(outputFunction=lambda s: None); result = ic.format(value). Custom serializer: ic.argToStringFunction = lambda x: f"{type(x).__name__}({x!r})". Logging: import logging; ic.configureOutput(outputFunction=logging.debug). Suppress in production: if not DEBUG: ic.disable(). Class/method context: works inside classes and closures. List comprehension: [ic(x) for x in items] — prints each. Generator: (ic(x) for x in items). Deep repr: works with dataclasses, Pydantic models, numpy arrays. Claude Code generates IceCream debug setups, logging integrations, and development workflow helpers.
CLAUDE.md for IceCream
## IceCream Stack
- Version: icecream >= 2.1 | pip install icecream
- Basic: ic(var) → "ic| var: value" to stderr | ic() → file + line number
- Passthrough: y = ic(expr) — prints AND returns expr for inline use
- Config: ic.configureOutput(prefix="app| ", includeContext=True)
- Disable: ic.disable() for production | ic.enable() to restore
- Custom: ic.argToStringFunction = lambda x: repr(x) | outputFunction=logging.debug
IceCream Debug Pipeline
# app/debug.py — icecream ic setup, context, logging integration, conditional debug, helpers
from __future__ import annotations
import logging
import os
import sys
from contextlib import contextmanager
from typing import Any, Callable, Generator, TypeVar
from icecream import IceCreamDebugger, ic
T = TypeVar("T")
log = logging.getLogger(__name__)
# ─────────────────────────────────────────────────────────────────────────────
# 1. Configuration helpers
# ─────────────────────────────────────────────────────────────────────────────
def configure_ic(
prefix: str = "ic| ",
include_context: bool = False,
output_fn: Callable | None = None,
arg_to_string: Callable | None = None,
) -> None:
"""
Configure the global ic() instance.
Example:
configure_ic(prefix="debug: ", include_context=True)
configure_ic(output_fn=logging.debug) # send to logging
"""
ic.configureOutput(
prefix=prefix,
includeContext=include_context,
outputFunction=output_fn or (lambda s: print(s, file=sys.stderr)),
argToStringFunction=arg_to_string or repr,
)
def configure_for_logging(
logger: logging.Logger | None = None,
level: int = logging.DEBUG,
include_context: bool = True,
prefix: str = "",
) -> None:
"""
Route ic() output to Python logging.
Example:
configure_for_logging()
ic(some_var) # goes to DEBUG log instead of stderr
"""
target = logger or log
def log_output(s: str) -> None:
target.log(level, s.rstrip())
ic.configureOutput(
prefix=prefix,
includeContext=include_context,
outputFunction=log_output,
)
def configure_for_production() -> None:
"""Disable ic() — call at startup in non-dev environments."""
ic.disable()
def configure_for_development(include_context: bool = True) -> None:
"""Enable ic() with full context — call at startup in dev."""
ic.enable()
ic.configureOutput(
prefix="ic| ",
includeContext=include_context,
outputFunction=lambda s: print(s, file=sys.stderr),
)
def auto_configure(debug_env_var: str = "DEBUG") -> None:
"""
Enable ic() if DEBUG env var is set, disable otherwise.
Example:
auto_configure() # reads os.environ["DEBUG"]
auto_configure("APP_DEBUG")
"""
if os.getenv(debug_env_var, "").lower() in ("1", "true", "yes"):
configure_for_development()
else:
configure_for_production()
# ─────────────────────────────────────────────────────────────────────────────
# 2. Scoped / contextual ic instances
# ─────────────────────────────────────────────────────────────────────────────
def make_ic(
prefix: str,
include_context: bool = True,
enabled: bool = True,
) -> IceCreamDebugger:
"""
Create a named ic() instance with a custom prefix.
Useful for distinguishing output from different modules.
Example:
db_ic = make_ic("db| ")
api_ic = make_ic("api| ")
db_ic(query) # "db| query: SELECT ..."
api_ic(status) # "api| status: 200"
"""
debugger = IceCreamDebugger(prefix=prefix)
debugger.configureOutput(prefix=prefix, includeContext=include_context)
if not enabled:
debugger.disable()
return debugger
@contextmanager
def ic_context(
prefix: str | None = None,
include_context: bool = True,
enabled: bool = True,
) -> Generator[IceCreamDebugger, None, None]:
"""
Temporary ic() with different settings inside a with block.
Restores previous settings on exit.
Example:
with ic_context(prefix="loop| ") as d:
for i in range(5):
d(i)
"""
debugger = IceCreamDebugger()
debugger.configureOutput(
prefix=prefix or "ic| ",
includeContext=include_context,
)
if not enabled:
debugger.disable()
yield debugger
@contextmanager
def ic_capture() -> Generator[list[str], None, None]:
"""
Capture ic() output as a list of strings instead of printing.
Example:
with ic_capture() as lines:
ic(x, y)
for line in lines:
do_something(line)
"""
captured: list[str] = []
original_fn = None
try:
# Save current output function via reconfiguring
captured_fn = lambda s: captured.append(s.rstrip())
ic.configureOutput(outputFunction=captured_fn)
yield captured
finally:
# Restore defaults
ic.configureOutput(outputFunction=lambda s: print(s, file=sys.stderr))
# ─────────────────────────────────────────────────────────────────────────────
# 3. Passthrough helpers
# ─────────────────────────────────────────────────────────────────────────────
def ic_return(value: T, label: str | None = None) -> T:
"""
Debug-print a value with optional label and return it unchanged.
Drop-in for inline ic() with an explicit name.
Example:
result = fetch_user(uid) |> ic_return("user") |> enrich_user
result = ic_return(expensive_compute(x))
"""
if label:
print(f"ic| {label}: {repr(value)}", file=sys.stderr)
else:
ic(value)
return value
def trace(fn: Callable) -> Callable:
"""
Decorator: ic() the function's args and return value.
Example:
@trace
def add(a, b):
return a + b
add(3, 4)
# ic| add args: (3, 4), {} → 7
"""
from functools import wraps
@wraps(fn)
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
label = fn.__qualname__
print(
f"ic| {label}({', '.join(repr(a) for a in args)}"
+ (f", {kwargs!r}" if kwargs else "") + f") → {result!r}",
file=sys.stderr,
)
return result
return wrapper
def ic_list(items: list[T], label: str = "item") -> list[T]:
"""
ic() each item in a list and return the list unchanged.
Example:
processed = ic_list(user_ids, label="user_id")
"""
for i, item in enumerate(items):
print(f"ic| {label}[{i}]: {item!r}", file=sys.stderr)
return items
def ic_dict(d: dict, label: str = "dict") -> dict:
"""
ic() each key-value pair and return the dict unchanged.
Example:
config = ic_dict(load_config(), label="config")
"""
for k, v in d.items():
print(f"ic| {label}[{k!r}]: {v!r}", file=sys.stderr)
return d
# ─────────────────────────────────────────────────────────────────────────────
# 4. Custom serializers
# ─────────────────────────────────────────────────────────────────────────────
def rich_arg_to_string(obj: Any) -> str:
"""
Custom argToStringFunction that shows type + truncated repr.
Useful for large objects (DataFrames, tensors).
Example:
ic.argToStringFunction = rich_arg_to_string
ic(my_dataframe) # "DataFrame(shape=(100, 5), dtype=...)"
"""
type_name = type(obj).__name__
# NumPy arrays
try:
import numpy as np
if isinstance(obj, np.ndarray):
return f"ndarray(shape={obj.shape}, dtype={obj.dtype})"
except ImportError:
pass
# DataFrames
try:
import pandas as pd
if isinstance(obj, pd.DataFrame):
return f"DataFrame(shape={obj.shape}, cols={list(obj.columns)[:5]})"
if isinstance(obj, pd.Series):
return f"Series(len={len(obj)}, dtype={obj.dtype})"
except ImportError:
pass
# Long strings
r = repr(obj)
if len(r) > 200:
return r[:200] + f"... <{len(r)} chars, {type_name}>"
return r
def use_rich_serializer() -> None:
"""Switch ic() to use rich_arg_to_string for large-object printing."""
ic.argToStringFunction = rich_arg_to_string
# ─────────────────────────────────────────────────────────────────────────────
# 5. Production guard pattern
# ─────────────────────────────────────────────────────────────────────────────
class DebugSession:
"""
Context manager for a temporary debug session.
Enables ic(), runs code, then disables ic() again.
Example:
with DebugSession(prefix="bug: "):
result = complex_pipeline(data)
"""
def __init__(
self,
prefix: str = "ic| ",
include_context: bool = True,
) -> None:
self.prefix = prefix
self.context = include_context
def __enter__(self) -> "DebugSession":
ic.enable()
ic.configureOutput(
prefix=self.prefix,
includeContext=self.context,
)
return self
def __exit__(self, *_) -> None:
ic.disable()
def __call__(self, *args) -> Any:
return ic(*args)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
# Basic usage
configure_for_development()
x = 42
name = "Claude"
data = {"key": "value", "nums": [1, 2, 3]}
print("=== Basic ic() ===")
ic(x)
ic(name, x)
ic(1 + 1) # expression, not variable name
ic() # print file + line
print("\n=== Passthrough ===")
result = ic(x * 2) # prints AND assigns
print(f" result = {result}")
print("\n=== Named instance ===")
api = make_ic("api| ")
api({"status": 200, "body": "ok"})
print("\n=== Trace decorator ===")
@trace
def multiply(a: int, b: int) -> int:
return a * b
multiply(6, 7)
print("\n=== ic_capture ===")
with ic_capture() as lines:
ic(x)
ic(name)
print(f" Captured {len(lines)} line(s):")
for line in lines:
print(f" {line}")
print("\n=== DebugSession ===")
ic.disable() # disable globally first
try:
ic(x) # silent — disabled
except Exception:
pass
with DebugSession(prefix="session| "):
ic(x) # prints inside context
print(" (ic disabled again after session)")
print("\n=== Custom serializer ===")
use_rich_serializer()
ic("a" * 250) # truncated repr
print("\nDone.")
For the print() / pprint alternative — bare print() requires you to type the variable name twice (print("x:", x)) and doesn’t include context (file/line); ic(x) automatically names the expression, prints ic| x: 42, and optionally shows the filename and line number — use print() for production logging/output, ic() as a drop-in replacement during interactive debugging that adds zero boilerplate. For the logging alternative — Python’s logging module is the right tool for structured, level-gated, destination-flexible production logging; IceCream is a development-time inspection tool designed to be added and removed quickly — use logging for anything you want to see in CI/production, ic() when you’re actively stepping through logic and want to inspect intermediate values with minimal typing. The Claude Skills 360 bundle includes IceCream skill sets covering configure_ic()/configure_for_logging()/auto_configure() setup, configure_for_production()/configure_for_development(), make_ic() named instances, ic_context() scoped config, ic_capture() output capture, ic_return()/trace()/ic_list()/ic_dict() passthrough helpers, rich_arg_to_string() for DataFrames/arrays, use_rich_serializer(), and DebugSession context manager. Start with the free tier to try developer debugging and print inspection code generation.