Library / SDK
gruns/icecream avatar
gruns/icecream

IceCream (ic): a drop-in replacement for print() debugging in Python

🍦 Never use print() to debug again.

10,113 stars234 forksPythonMIT

At a glance

What is it?
IceCream is a small Python library that prints the expressions you pass it along with their values, and the file, line and function when you pass nothing. It is aimed at developers who debug by printing, and the trade-offs are mostly about what it does not do.
Who is it for?
Adopt IceCream if your debugging loop is print statements and you want the expression text, the value and the call site without typing them by hand; the install is one PyPI package and ic() returns its arguments, so it drops into existing code. Do not adopt it as a logging framework or a step debugger: there is no log level, no handler configuration and no breakpoint control, and the README's own fallback snippet treats it as something that may be absent in production.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 40 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What IceCream replaces, and who reaches for it

The README opens with a question: do you ever use print() or log() to debug your code? IceCream, or ic for short, is the answer for people who say yes. The problem it targets is narrow and real. A print statement shows you a value but not the expression that produced it, so you either write the expression twice or lose track of which call printed what. IceCream's ic() inspects its own arguments and prints both sides. The README's first example is ic(foo(123)) printing ic| foo(123): 456, and the second shows the same for a nested lookup and a class attribute: ic| d['key'][1]: 'one' and ic| klass.attr: 'yep'. The intended user is a Python 3 or PyPy3 developer in an interactive debugging session, not someone instrumenting a service. The README states the library is well tested and permissively licensed, and that it is maintained by Jakeroid (Ivan Karabadzhak) with support from Lunal. The last push to the repository was on 2026-08-21, and the most recent release listed is v2.2.0 from 2026-04-03.

How ic() reads its own source and prints context

The mechanism is argument introspection plus call-site inspection. When you call ic() with arguments, it renders each argument twice: once as the source text of the expression, once as the value. Values go through a serializer that defaults to pprint.pformat(), so dictionaries and nested structures come out formatted rather than on one line. When you call ic() with no arguments, it has nothing to render, so instead it reports where the call happened: the README shows output like ic| example.py:4 in foo() and ic| example.py:11 in foo(). That gives you a cheap execution trace without adding labels by hand. The call site is also available on argument calls if you turn on context, since configureOutput() takes an includeContext flag alongside contextAbsPath for absolute paths. Output goes to stderr by default, and the default prefix is the string ic| , both of which are configurable. There is no daemon, no file format and no background thread in the described design; ic() formats a string and hands it to an output function. That simplicity is the whole architecture, and it is why the library is small enough to read in one sitting.

Installing icecream and a first real ic() call

The package is published on PyPI as icecream, and the README's badges point at pypi.python.org/pypi/icecream. Install it with pip:

bash
pip install icecream

Then import ic and call it on something you would otherwise print. The README's own example defines a function and passes a call to it:

python
from icecream import ic

def foo(i):
    return i + 333

ic(foo(123))

Running that prints ic| foo(123): 456 to stderr. If you want the call site instead of a value, call ic() with no arguments inside a function and you get a line naming the file, line number and parent function. One property matters more than it looks: ic() returns its arguments. The README shows b = half(ic(a)) printing ic| a: 6 and leaving b equal to 3, so you can wrap an expression in ic() inside existing code without changing what that code computes. If you want the string rather than the side effect, ic.format(s) returns the same output as a string, which the README demonstrates producing ic| s: 'sup'.

Turning ic() off, redirecting it, and teaching it new types

configureOutput(prefix, outputFunction, argToStringFunction, includeContext, contextAbsPath) is the single configuration entry point, and each parameter changes one part of the pipeline. prefix replaces the default ic| and can be a string or a callable; the README shows a function returning a Unix timestamp so each line is stamped. outputFunction receives the formatted string once per call instead of it being written to stderr, which is how you route ic() output into logging.warning or anything else. argToStringFunction replaces the serializer. The default is icecream.argumentToString, which dispatches on type using functools.singledispatch and exposes register, unregister and a registry property. The README's numpy example registers a handler for np.ndarray and gets ic| x: ndarray, shape=(1, 2), dtype=float64 instead of a dumped array. This is the part of the design worth copying: rather than asking users to subclass a formatter, it lets them attach a function to a class. The other two switches are ic.disable() and ic.enable(). Between them, ic() prints nothing, but it still returns its arguments, so disabling it does not break code that depends on the return value. That pair is the closest thing to a log-level control the library has.

The install() trick and why it is a double-edged tool

For codebases where importing ic in every file is friction, the README offers from icecream import install followed by install(), which adds ic() to the builtins module. Because builtins is shared across all files the interpreter imports, a module imported afterwards can call ic(x) with no import of its own; the README demonstrates exactly that with a root A.py calling install() and a B.py that only defines foo() and calls ic(x). uninstall() reverses it. This is convenient and also the sharpest edge in the project. A name that appears from nowhere in a file is hard to trace, and any tool that reasons about imports, such as a linter or a type checker, will not see the dependency. The README frames install() as a way to avoid importing in every file, which is honest about the intent, but it does not document what happens if two libraries both install their own ic, or how uninstall() interacts with a partially imported module graph. If you use it, keep it in a single entry point script rather than scattered across a package.

Where IceCream stops: production, concurrency and non-Python work

IceCream is a debugging aid, and the README treats it that way. Its own fallback snippet, offered for environments where IceCream is not installed, defines ic as a lambda that returns its argument or None, which is a clear signal that the library is not meant to ship as your production output path. There is no log level, no handler hierarchy, no rotation and no structured output; if you need those, ic() is the wrong tool and the standard logging module is the right one. Concurrency is another gap. The README documents a global prefix and global enable/disable state, and it does not document thread-local or context-local configuration, so a prefix set for one request or one thread applies everywhere in the process. For a single-threaded debugging session that is fine; for a web server handling concurrent requests it is a reason to reach for something else. Finally, the project is Python-only. The topics list python and python3, the setup metadata is Python, and nothing in the README suggests bindings for other languages. If your bug is in JavaScript or Go, this library has nothing to offer.

IceCream against pdb and against logging

The obvious alternative is pdb, the Python standard library's interactive debugger. The difference is not quality but posture. pdb stops the program and lets you inspect state at a breakpoint, stepping forward and backward through frames; IceCream never stops anything and never lets you inspect state you did not explicitly pass to it. If your question is what is the value of this expression right here, ic() answers it in one line. If your question is how did this object reach this state, you want a debugger. The second alternative is logging, which shares ic()'s non-stopping behaviour but differs in almost everything else: logging has levels, named loggers, handlers, formatters and configuration files, and it is designed to run in production. IceCream has none of that and does not pretend to. The honest framing is that ic() sits between the two: more informative than print(), far less capable than either pdb or logging. The README's own comparison is with print() and log(), and it wins that comparison on the specific axis of showing the expression text next to the value.

Licence, maintenance and what an upgrade costs

The licence is MIT, stated in the repository metadata and in LICENSE.txt, and the README calls the project permissively licensed. For most users that means you can vendor it, modify it and ship it inside a closed product, provided you keep the copyright notice; that is a general description of MIT, not legal advice, and anything unusual about your distribution should go to a lawyer. Maintenance looks current rather than dormant: the repository is not archived, the last push was on 2026-08-21, and the release list shows v2.2.0 on 2026-04-03, v2.1.11 on 2026-04-03 and v2.1.10 on 2026-01-21. The upgrade surface is small because the public API is small: ic, install, uninstall, format, enable, disable, configureOutput and argumentToString. The changelog file at the repository root is where the project records what changed between releases, and reading it before a version bump is cheaper than reading the diff. The one thing to watch on upgrade is the argumentToString registry, since the README describes register, unregister and a registry property, and any change to dispatch behaviour would affect custom handlers you have attached to your own classes.

Editorial conclusion

Adopt IceCream if your debugging loop is print statements and you want the expression text, the value and the call site without typing them by hand; the install is one PyPI package and ic() returns its arguments, so it drops into existing code. Do not adopt it as a logging framework or a step debugger: there is no log level, no handler configuration and no breakpoint control, and the README's own fallback snippet treats it as something that may be absent in production. Before committing, verify what the current release does with ic() output in a threaded program and whether the default stderr destination fits your pipeline, since the README documents outputFunction as the way to redirect it and does not document thread-local prefixes.

Frequently asked questions

How do I install IceCream for Python?

Install it from PyPI with pip install icecream, then import ic with from icecream import ic. The README's badges link to the PyPI page at pypi.python.org/pypi/icecream.

What does ic() print when I pass it a variable?

It prints both the source text of the expression and its value, so ic(foo(123)) produces ic| foo(123): 456. Values are formatted with pprint.pformat() by default.

Can I turn IceCream off in production?

Yes. ic.disable() stops ic() from printing and ic.enable() turns it back on, and ic() still returns its arguments while disabled so existing code keeps working. The README also gives a try/except ImportError fallback for environments where IceCream is not installed.

How do I make ic() show the filename and line number?

Call ic() with no arguments inside the function you are tracing, and it prints the calling filename, line number and parent function, for example ic| example.py:4 in foo(). You can also set includeContext through ic.configureOutput().

How do I send ic() output somewhere other than stderr?

Pass an outputFunction to ic.configureOutput(); it is called once per ic() call with the formatted string instead of that string going to stderr. The README shows routing it into logging.warning.

Does ic() work as a logging replacement?

No. The README compares ic() with print() and log() as debugging habits, but IceCream has no log levels, handlers or production configuration; the README's fallback import snippet assumes IceCream may be absent outside development.

Official sources

  1. gruns/icecream on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/gruns-icecream.svg)](https://hysenlabs.com/projects/gruns-icecream)