
Hello! This is Atsushi Yasumoto, a data scientist at HACARUS.
In this article, I will give an overview of the icecream package, a useful tool for debugging print while using Python. I will also introduce a customized feature we have added at HACARUS as part of our work. Before trying this for yourself, please use pip install icecream
to install the v2.1.2 version of icecream.
To start, let’s look at a quick example of print debugging. Suppose you have an assignment statement of y = x + 1
in a function, and you want to see the result of executing x + 1
.
If we simply use print(y)
, it is difficult to tell what kind of processing results in y. It is also difficult to distinguish if there are other print
functions being executed in the script. To account for this, we would write print("x + 1:", y)
, which is tedious to write each time the debugging point is changed.
However, icecream provides an easy fix to this problem with the icecream.ic
function (referred to as ic
for the remainder of this article). Using the ic
function y = x + 1
can be rewritten simply as y = ic(x + 1)
. Another advantage of the ic
function is that it returns its value as-is, unlike the print
function, which has a return value of None
. This allows it to be used in the middle of an expression, such as y = ic(x + 1)
.
Below is a practical example of the ic
function. Here, it is important to note that the ic
function is used within the f function, and when executed, ic| x + 1: 2
is displayed.
>>> from icecream import ic
>>>
>>> def f(x):
>>> y = ic(x + 1)
>>> return y
>>>
>>> # icecreamでデバッグしながら実行
>>> y = f(1)
ic| x + 1: 2
>>>
>>> # 返り値の確認
>>> print(y)
2
Here, ic
uses the pprint.pprint
function internally to display the contents of objects in a clear manner. (refer to 「Pythonのpprintの使い方(リストや辞書を整形して出力)」).
While powerful, there are a few limitations of the pprint.pprint
function. Since it is provided by the standard library, there are situations where it is not powerful enough to display instances of classes provided by external modules such as the Numpy Array. For example, when a Numpy Array of 100 rows and 100 columns is put into the ic
function, the user can only understand that it contains a large array of real numbers. Unfortunately, in data science, the shape or the distribution of values may be more useful than the object values themselves.
>>> import numpy as np
>>>
>>> x = np.random.RandomState(0).normal(size=(100, 100))
>>> ic(x)
ic| x: array([[ 1.76405235, 0.40015721, 0.97873798, ..., 1.78587049,
0.12691209, 0.40198936],
[ 1.8831507 , -1.34775906, -1.270485 , ..., 0.82350415,
2.16323595, 1.33652795],
[-0.36918184, -0.23937918, 1.0996596 , ..., 0.58295368,
-0.39944903, 0.37005589],
...,
[-0.05524379, 0.13064302, 0.44069106, ..., 0.97220715,
-0.91895048, 0.6632405 ],
[-0.1334914 , -1.56637034, -1.74865144, ..., 0.0708476 ,
2.43572851, 0.9716812 ],
[-0.93296221, 2.86520354, -1.79204799, ..., 0.51687218,
-0.03292069, 1.29811143]])
As a way to get around these limitations, at HACARUS, we have implemented a flexible and easy way to customize the ic
function. We then proposed it to the head office and it has been adopted within the company.
Earlier, I mentioned that the ic
function uses the pprint.pprint
function internally. A function called icecream.argumentToString
is also used to wrap it. Here, we have added a tool called singledispatch
, which selects processing based on the type of the argument. This allows the user to add or remove processing based on the type as well.
We can also add processing by defining a function using the argumentToString.register
method as a decorator. Here, the key is to annotate the type with obj: np.ndarray
. The ic
function will then choose to process the input data according to its type.
For instance, let’s add a function that prints the shape, the dtype, and the minimum and maximum values of an array when a Numpy Array is passed to the ic
function.
>>> from icecream import ic, argumentToString
>>> import numpy as np
>>>
>>> # Register a function to summarize numpy array
>>> @argumentToString.register
>>> def argumentToString_ndarray(obj: np.ndarray):
>>> return (
>>> f"ndarray, shape={obj.shape}, dtype={obj.dtype}, "
>>> f"min={obj.min()}, max={obj.max()}"
>>> )
>>>
>>> ic(x)
ic| x: ndarray, shape=(100, 100), dtype=float64, min=-3.740100637951779, max=3.8016602149671153
Here we have written the process for the Numpy Array in the argumentToString_ndarray
function, but there is no restriction on the name. If you do not plan to call the function by name, you can use _
as the name.
Next, to see a list of registered functions by type, let’s refer to the argumentToString.registry
property, where the content is an instance of MappingProxyType
. It might help to think of it as a read-only version of dict
.
>>> print(argumentToString.registry)
mappingproxy({object: <function icecream.icecream.argumentToString(obj)>,
numpy.ndarray: <function __main__._(obj: numpy.ndarray)>})
To delete a registered function, specify the type of processing to be deleted in the argumentToString.unregister
method.
>>> argumentToString.unregister(np.ndarray)
The function implemented this time is based on the functools.singledispatch
function (reference 公式ドキュメント). This function can be implemented simply by adding only two lines as follows:
+ from functools import singledispatch
+ @singledispatch
def argumentToString(obj):
However, the actual Pull Request became a bit more complex at the request of reviewers.
Find the code on Github (https://github.com/gruns/icecream/pull/115).
- Python 2 does not have
functools.singledispatch
, so it is not necessary for single dispatching, but I would like theic
functions to remain available. - The original
functools.singledispatch
has a function to register functions but isn’t able to delete them.
In the end, I am surprised that you are still willing to support Python 2. I think it is one of the best parts of OSS development is enjoying the differences in philosophy in this area. The latter was also a problem to implement, but fortunately, I was able to find the right answer on StackOverflow (ドンピシャの回答) and it turned out to be a success.
I hope that all of you are able to enjoy using the icecream package! More information about the package and porting it to other languages can be obtained from the official repository.