Python's IceCream Package: Effortless Debugging with Minimal Code
Published: 2023-10-29 22:51:33

## 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]](https://towardsdatascience.com/introducing-icecream-never-use-print-to-debug-your-python-code-again-d8f2e5719f8a). 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`

```python
from icecream import ic

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

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

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

The output here would be:

```
6
12
20
30
```

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]](https://medium.com/@bobbymistery/debug-your-python-code-efficiently-with-icecream-package-10-advanced-examples-to-replace-print-820fef801cb0).

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

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
```

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:

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

In this case, the output would be:

```
30
ic| multiply(5, 6): 30
```

### Multiple Values

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

```python
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)
```

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
```

Note that ic() automatically separates the values with commas in the output [[2]](https://medium.com/@bobbymistery/debug-your-python-code-efficiently-with-icecream-package-10-advanced-examples-to-replace-print-820fef801cb0).

### Conditional Statements

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

```python
from icecream import ic

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

is_even(4)
is_even(5)
```

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
```

Note that ic() will only print the second argument (num) when the first argument (result) is True [[2]](https://medium.com/@bobbymistery/debug-your-python-code-efficiently-with-icecream-package-10-advanced-examples-to-replace-print-820fef801cb0).

### Iterables

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

```python
from icecream import ic

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

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

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
```

Note that ic() automatically prints the contents of the list [[2]](https://medium.com/@bobbymistery/debug-your-python-code-efficiently-with-icecream-package-10-advanced-examples-to-replace-print-820fef801cb0).

### Functions

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

```python
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)
```

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
```

Note that ic() automatically prints the return value of the function [[2]](https://medium.com/@bobbymistery/debug-your-python-code-efficiently-with-icecream-package-10-advanced-examples-to-replace-print-820fef801cb0).



## 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:

```python
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"
      }
    }
  ]
}
```

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


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/satw9h3a5vwayxxnpfn8.png)

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

```python
from icecream import ic

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

multiply_and_divide(2, 3)
```

The output will look like this:

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

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

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

```python
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)
```



## 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 ...](https://towardsdatascience.com/introducing-icecream-never-use-print-to-debug-your-python-code-again-d8f2e5719f8a)
2. [“Debug Your Python Code Efficiently with IceCream Package: 10 Advanced Examples to Replace Print Statements” | by Daniel Wu | Medium](https://medium.com/@bobbymistery/debug-your-python-code-efficiently-with-icecream-package-10-advanced-examples-to-replace-print-820fef801cb0)
3. [Stop Using Print to Debug in Python. Use Icecream Instead | by Khuyen Tran | Towards Data Science](https://towardsdatascience.com/stop-using-print-to-debug-in-python-use-icecream-instead-79e17b963fcc)