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
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))
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].
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:
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:
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].
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)
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].
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])
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].
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)
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].
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"
}
}
]
}
We can print it using ic
to get some nice indentation and color
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)
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)
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)
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.
Top comments (0)