DEV Community

PGzlan
PGzlan

Posted on

Python's IceCream Package: Effortless Debugging with Minimal Code

Introduction

Often times when programming in Python, things don't work the way we expect them to work. We implement a function, and the function does not deliver the results that we were expect from it. Well, at least not all the time, and we have no clear reason to identify this behavior. Imagine some function taking some parameters param1...paramN, then these parameters are used in a loop, which contains some conditional statements. So we have ourselves a complicated function, with loops, and conditions and things can become rather unpredictable.

Of course the proper way to debug such a function would be by leveraging a debugger in your IDE (Integrated Development Environment) or your code editor by setting up break points to check what modifications occur to the variables inside. This can be rather tedious and a lot of time you find yourself avoiding the debugger and debugging mode. We just want to execute the code as it is right now, with a little bit of additional information so what ends up happening is just using good ol' print statements.

Debugging is an essential part of software development, and print statements have long been used to debug code. However, traditional print statements can be tedious to write and can clutter code. That's where the icecream (ic) package comes in. IceCream is a Python library that makes debugging effortless and readable with minimal code [1]. In this blog post, we will explore the features of the IceCream package and provide code examples to demonstrate its usage.

Usage

Let's say as a simple example, we have some function multiply that takes a and b as parameters and returns the simple value of a * b



from icecream import ic

def multiply(a, b):
    return a * b


Enter fullscreen mode Exit fullscreen mode

To test this function, we can write it in the following manner



print(multiply(2, 3))
print(multiply(3, 4))
print(multiply(4, 5))
print(multiply(5, 6))


Enter fullscreen mode Exit fullscreen mode

The output here would be:



6
12
20
30


Enter fullscreen mode Exit fullscreen mode

This seems relatively simple to trace. That would not hold in cases we have a longer list of values to test. If this is a more complex function, we cannot predict immediately by looking at the function call what the result is going to. It would be nicer to have the function call to what is actually being called and what is the result of this function call and this is exactly what icecream ic does [2].



ic(multiply(2, 3))
ic(multiply(3, 4))
ic(multiply(4, 5))
ic(multiply(5, 6))


Enter fullscreen mode Exit fullscreen mode

The output will look like this:



ic| multiply(2, 3): 6
ic| multiply(3, 4): 12
ic| multiply(4, 5): 20
ic| multiply(5, 6): 30


Enter fullscreen mode Exit fullscreen mode

One of the neat things about ic is, unlike print (which have a None return value), using ic will not only provide a logging message, it will also get the returned value stored into the variable you assign it to:



res = ic(multiply(5, 6))
print(res)


Enter fullscreen mode Exit fullscreen mode

In this case, the output would be:



30
ic| multiply(5, 6): 30


Enter fullscreen mode Exit fullscreen mode

Multiple Values

You can also use ic() to print multiple values at once. Here's an example:



from icecream import ic

def multiply_and_divide(a, b):
    result = a * b
    ic(result, a/b)
    return result, a/b

multiply_and_divide(2, 3)


Enter fullscreen mode Exit fullscreen mode

In this example, we're using ic() to print the result of the multiplication operation and the result of the division operation. The output will look like this:



ic| result: 6, a/b: 0.6666666666666666


Enter fullscreen mode Exit fullscreen mode

Note that ic() automatically separates the values with commas in the output [2].

Conditional Statements

You can use ic() in conditional statements to print values only when certain conditions are met. Here's an example:



from icecream import ic

def is_even(num):
    result = num % 2 == 0
    ic(result, num)
    return result

is_even(4)
is_even(5)


Enter fullscreen mode Exit fullscreen mode

In this example, we're using ic() to print the result of the conditional statement and the input value only when the input value is even. The output will look like this:



ic| result: True, num: 4
ic| result: False


Enter fullscreen mode Exit fullscreen mode

Note that ic() will only print the second argument (num) when the first argument (result) is True [2].

Iterables

You can use ic() to print the contents of iterables such as lists, tuples, and dictionaries. Here's an example:



from icecream import ic

def sum_list(numbers):
    result = sum(numbers)
    ic(numbers, result)
    return result

sum_list([1, 2, 3, 4, 5])


Enter fullscreen mode Exit fullscreen mode

In this example, we're using ic() to print the input list and the sum of the list. The output will look like this:



ic| numbers: [1, 2, 3, 4, 5], result: 15


Enter fullscreen mode Exit fullscreen mode

Note that ic() automatically prints the contents of the list [2].

Functions

You can use ic() to print the result of a function call. Here's an example:



from icecream import ic

def multiply(a, b):
    result = a * b
    return result

def square_and_multiply(a, b):
    result = multiply(a**2, b)
    ic(result)
    return result

square_and_multiply(2, 3)


Enter fullscreen mode Exit fullscreen mode

In this example, we're using ic() to print the result of the multiply function call within the square_and_multiply function. The output will look like this:



ic| result: 12


Enter fullscreen mode Exit fullscreen mode

Note that ic() automatically prints the return value of the function [2].

Nifty things about ic

Using ic provides some nice utility in terms of readability, especially for large dictionaries. Let's say we have the following dict:



data = {
  "contacts": [
    {
      "name": "John Doe",
      "contactMethods": {
        "phone": "123-456-7890",
        "email": "john.doe@example.com"
      }
    },
    {
      "name": "Jane Smith",
      "contactMethods": {
        "phone": "987-654-3210",
        "email": "jane.smith@example.com"
      }
    },
    {
      "name": "Robert Johnson",
      "contactMethods": {
        "phone": "555-123-4567",
        "email": "robert.johnson@example.com"
      }
    },
    {
      "name": "Emily Davis",
      "contactMethods": {
        "phone": "111-222-3333",
        "email": "emily.davis@example.com"
      }
    },
    {
      "name": "Michael Brown",
      "contactMethods": {
        "phone": "444-555-6666",
        "email": "michael.brown@example.com"
      }
    }
  ]
}


Enter fullscreen mode Exit fullscreen mode

We can print it using ic to get some nice indentation and color

Image description

If we call ic() at a certain part of the function like this:



from icecream import ic

def multiply_and_divide(a, b):
    result = a * b
    ic()
    return result, a/b

multiply_and_divide(2, 3)


Enter fullscreen mode Exit fullscreen mode

The output will look like this:



ic| <ipython-input-13-5d4984d3accb>:5 in multiply_and_divide() at 21:50:10.905
Out[13]: (6, 0.6666666666666666)


Enter fullscreen mode Exit fullscreen mode

This can be useful to determine where far we have reached in a certain function. You can also enable or disable it selectively



multiply_and_divide(2, 3)
multiply_and_divide(4, 5)
ic.disable()
multiply_and_divide(5, 6)
ic.enable()
multiply_and_divide(6, 7)

Enter fullscreen mode Exit fullscreen mode


ic| <ipython-input-13-5d4984d3accb>:5 in multiply_and_divide() at 21:59:35.824
ic| <ipython-input-13-5d4984d3accb>:5 in multiply_and_divide() at 21:59:35.826
ic| <ipython-input-13-5d4984d3accb>:5 in multiply_and_divide() at 21:59:35.828
Out[14]: (42, 0.8571428571428571)

Enter fullscreen mode Exit fullscreen mode




Conclusion

IceCream is a powerful tool for debugging Python code that replaces the traditional print statement. It provides detailed information about variables, expressions, and function calls that make it easy to identify and fix errors in your code. With its customizable output, logging capabilities, and ability to access external modules, IceCream is a versatile debugging tool that can save you time and frustration. By using the examples provided in this blog post, you can start using IceCream in your own Python projects and improve your debugging workflow.


References

  1. Introducing IceCream: Never Use Print() To Debug Your ...
  2. “Debug Your Python Code Efficiently with IceCream Package: 10 Advanced Examples to Replace Print Statements” | by Daniel Wu | Medium
  3. Stop Using Print to Debug in Python. Use Icecream Instead | by Khuyen Tran | Towards Data Science

Top comments (0)