デバッグ出力を使いやすくする「icecream」を試す
恥ずかしながら知らなかった。
GitHubレポジトリ
IceCream — もうprint()を使ってデバッグする必要はありません
あなたはコードをデバッグするために
print()
やlog()
を使ったことがありますか?もちろん使ったことがあるでしょう。アイスクリーム(略してic
)は、printデバッグを少しだけ楽にしてくれます。
ic()
はprint()
のようなものですが、もっと便利です:
- 変数や式とその値を一緒に出力します。
- タイピングが60%速くなります。
- データ構造がフォーマットされ、美しく表示されます。
- 出力がシンタックスハイライトされます。
- プログラムのコンテキスト(ファイル名、行番号、親関数など)をオプションで含むことができます。
アイスクリームは十分にテストされており、寛容なライセンスのもと提供され、Python 3およびPyPy3をサポートしています。
Colaboratoryで。
パッケージインストール
!pip install icecream
from icecream import ic
def foo(i):
return i + 333
ic(foo(123))
ic| foo(123): 456
456
notebookなので2重に出力されてしまっているが、なるほど。
辞書やクラスなども。
d = {'key': {1: 'one'}}
ic(d['key'][1])
class klass():
attr = 'yep'
ic(klass.attr)
ic| d['key'][1]: 'one'
ic| klass.attr: 'yep'
'yep'
よくある処理フローのステップごとにprint分を入れるようなやつ。icを使わない場合だとこう。
def first():
return "1番目の関数を実行しました"
def second():
return "2番目の関数を実行しました"
def third():
return "3番目の関数を実行しました"
def foo(expression):
print("1番目の関数を実行します")
first()
if expression:
print("2番目の関数を実行します")
second()
else:
print("3番目の関数を実行します")
third()
foo(True)
1番目の関数を実行します
2番目の関数を実行します
foo(False)
1番目の関数を実行します
3番目の関数を実行します
icを使うとこうかける。
def first():
return "1番目の関数を実行しました"
def second():
return "2番目の関数を実行しました"
def third():
return "3番目の関数を実行しました"
def foo(expression):
ic()
first()
if expression:
ic()
second()
else:
ic()
third()
foo(True)
ic| <ipython-input-19-12dd67137a08>:14 in foo() at 19:32:25.935
ic| <ipython-input-19-12dd67137a08>:18 in foo() at 19:32:25.998
foo(False)
ic| <ipython-input-19-12dd67137a08>:14 in foo() at 19:33:07.978
ic| <ipython-input-19-12dd67137a08>:21 in foo() at 19:33:08.037
icは引数を返すので、既存のコードにも組み込みやすい。例えばこういうコード。
a = 6
def half(i):
return i / 2
b = half(a)
b
3.0
ここにicを組み込む。
a = 6
def half(i):
return i / 2
b = half(ic(a)) # ここ
b
ic| a: 6
3.0
これでも良さそう
a = 6
def half(i):
return i / 2
b = ic(half(a)) # ここ
b
ic| half(a): 3.0
3.0
あー、これは便利だねえ。
ic()
は標準エラー出力になるが、ic.format(*args)
だと文字列として出力される。
s = 'sup'
out = ic.format(s)
print(type(out))
print(out)
<class 'str'>
ic| s: 'sup'
ic.disable()
とic.enable()
でic()
の出力の無効・有効を制御できる。
ic(1)
ic.disable()
ic(2)
ic.enable()
ic(3)
ic| 1
ic| 3
複数ファイルで扱う場合、呼び出し側となるルートのファイル内でinstall()
しておけば、ic()
がビルトインモジュールに追加sれるので、呼び出される側でもic()
を直接呼び出すことができる。逆にuninstall()
でこれを無効にすることもできる。
%%writefile A.py
from icecream import install
# `ic()`をすべてのファイルで利用可能にする
install()
# 別のファイルの関数を呼び出す
from B import foo
print("A.pyからB.pyのfoo()を呼びます:")
foo()
%%writefile B.py
def foo():
x = 42
ic(x) # `ic()`を直接使用可能
!python A.py
A.pyからB.pyのfoo()を呼びます:
ic| x: 42
icecream
がインストールされていない場合は以下のようにチェックすると良い
try:
from icecream import ic
except ImportError: # `icecream`がインストールされていない場合でもエラーにならない
ic = lambda *args: None # `ic()`が何も出力しないダミー関数になる
icのデバッグ出力をカスタマイズできる
パラメータ | デフォルト値 | 説明 |
---|---|---|
prefix |
'ic| ' |
出力の先頭に付ける文字列または動的関数 |
outputFunction |
標準エラー | 出力をカスタム関数にリダイレクト |
argToStringFunction |
PrettyPrint | 引数の文字列化のカスタマイズ |
includeContext |
False |
ファイル名、行番号、関数名を出力に含める |
contextAbsPath |
False |
絶対パスでファイル名を表示(includeContext=True が必要) |
スタティックな文字列でプレフィックスを指定
from icecream import ic
ic.configureOutput(prefix='デバッグ -> ')
ic('テスト')
デバッグ -> 'テスト'
関数に渡して動的にプレフィックスを付ける
import time
from icecream import ic
def timestamp():
return f"{int(time.time())} |> "
ic.configureOutput(prefix=timestamp)
ic('テスト')
1732133988 |> 'テスト'
テスト
outputFunction
を使うと、出力を標準エラー出力ではなく、関数に文字列として渡すことができる。loggingと組み合わせるとこんな感じ。
import logging
from icecream import ic
logging.basicConfig(level=logging.WARNING)
def log_warning(message):
logging.warning(message)
ic.configureOutput(outputFunction=log_warning)
ic('警告メッセージ')
WARNING:root:1732134141 |> '警告メッセージ'
argToStringFunction
を使うと、ic()に渡された引数をカスタムなフォーマットで文字列にできる。まず、argToStringFunction
のデフォルトはicecream.argumentToString
、でこの内部でpprint.pformat()
が呼ばれている。
from icecream import ic, argumentToString
from pprint import pformat
data = {"key": "value", "nested": {"inner_key": [1, 2, 3]}}
# デフォルト
ic(data)
# argumentToString
print(argumentToString(data))
# pformat
print(pformat(data))
ic| data: {'key': 'value', 'nested': {'inner_key': [1, 2, 3]}}
{'key': 'value', 'nested': {'inner_key': [1, 2, 3]}}
{'key': 'value', 'nested': {'inner_key': [1, 2, 3]}}
カスタムな関数を指定してこれを書き換える。
from icecream import ic
def custom_string_format(value):
if isinstance(value, str):
return f"[文字列: {value} (長さ={len(value)})]"
return str(value)
ic.configureOutput(argToStringFunction=custom_string_format)
ic(42, "テスト")
WARNING:root:1732134435 |> 42, [文字列: テスト (長さ=3)]
さらにargumentToString
にはregister
/ unregister
メソッドがあり、特定のデータ型ごとにカスタム関数を登録・解除することができる。
numpy.ndarray
に対するフォーマット関数を登録
import numpy as np
from icecream import ic, argumentToString
# NumPy 配列に対するカスタム関数を登録
@argumentToString.register(np.ndarray)
def ndarray_to_string(array):
return f"NumPy Array (shape={array.shape}, dtype={array.dtype})"
# テストデータ
x = np.zeros((2, 3))
# デバッグ出力
ic(x)
ic| x: NumPy Array (shape=(2, 3), dtype=float64)
登録された型と関数はargumentToString.registry
で確認できる。
print(argumentToString.registry)
{<class 'object'>: <function argumentToString at 0x7fb546116200>, <class 'numpy.ndarray'>: <function ndarray_to_string at 0x7fb546ec5240>}
解除するとデフォルトのpprint.pformat
を使用した表示に戻る。
argumentToString.unregister(np.ndarray)
ic(x)
ic| x: array([[0., 0., 0.],
[0., 0., 0.]])
argumentToString
はfunctools.singledispatch
を使用しているので、特定の型やクラスだけでなく、そのサブクラスにも適用される。
from icecream import ic, argumentToString
# 親クラス
class Animal:
def __init__(self, name):
self.name = name
# サブクラス
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
# Animal 型に対するカスタム関数を登録
@argumentToString.register(Animal)
def animal_to_string(obj):
return f"Animal(name={obj.name})"
# データ
generic_animal = Animal("Generic")
specific_dog = Dog("Buddy", "Golden Retriever")
# デバッグ出力
ic(generic_animal)
ic(specific_dog)
ic| generic_animal: Animal(name=Generic)
ic| specific_dog: Animal(name=Buddy)
サブクラス専用の関数を登録することもできる。
# Dog 型に対するカスタム関数を登録
@argumentToString.register(Dog)
def dog_to_string(obj):
return f"Dog(name={obj.name}, breed={obj.breed})"
# 再度デバッグ出力
ic(generic_animal) # Animal 用の関数が適用される
ic(specific_dog) # Dog 用の関数が適用される
ic| generic_animal: Animal(name=Generic)
ic| specific_dog: Dog(name=Buddy, breed=Golden Retriever)
includeContext
を有効にすると、ic()
の出力にファイル名、行番号、関数名が含まれるようになる。デフォルトはfalse
%%writefile example.py
from icecream import ic
# includeContext を有効化
ic.configureOutput(includeContext=True)
def foo():
i = 42
ic(i)
foo()
!python example.py
ic| example.py:8 in foo()- i: 42
さらにcontextAbsPath
を組み合わせると、ファイル名が絶対パスになる。
%%writefile example.py
from icecream import ic
# includeContext と contextAbsPath を有効化
ic.configureOutput(includeContext=True, contextAbsPath=True)
def foo():
i = 42
ic(i)
foo()
!python example.py
ic| /content/example.py:8 in foo()- i: 42
VSCodeなどでは絶対パスの場合にリンクが付与されるので、デバッグ等に便利。