Сообщить об ошибке.

Модуль icecream в Python

Инспектирование кода при отладке проекта в Python

В самом начале работы над новым проектом или создании его прототипа, вряд ли кто сразу бросается настраивать модуль logging для отладки кода или инспектировании переменных. 85% разработчиков используют для этих целей функцию print().

Делать отладку немного приятнее, используя сторонний модуль icecream, или сокращенно ic. Этот модуль похож на функцию print(), но лучше и не требует специальной настройки, подобной модулю logging.

ВНИМАНИЕ: Модуль icecream НЕ работает в терминале Python и в случае попытки выполнить icecream.ic() будет ругаться, что не удалось получить доступ к базовому исходному коду для анализа. Не запускайте примеры в терминале, они будут работать, если запускать файл с кодом представленных ниже примеров.

Краткая характеристика модуля icecream:

  • печатает как выражения/имена переменных, так и их значения;
  • на 40% быстрее функции print();
  • легко отключить вывод отладочной информации;
  • может включать контекст программы: имя файла, родительскую функцию и номер строки;
  • модуль хорошо протестирован и поддерживает Python2, Python3, PyPy2 и PyPy3;

Установка модуля icecream в виртуальное окружение:

# создаем виртуальное окружение, если нет
$ python3 -m venv .venv --prompt VirtualEnv
# активируем виртуальное окружение 
$ source .venv/bin/activate
# ставим модуль icecream
(VirtualEnv):~$ python -m pip install -U icecream

Содержание.


Инспектирование/проверка переменных.

При отладке программы иногда необходимо вывести значения некоторых переменных или выражений, при этом функцию print() используют следующим образом:

print(foo('123'))
# или
print("foo('123')", foo('123'))

В данной ситуации очень поможет функцию ic() модуля icecream. С помощью аргументов, ic() проверяет себя и выводит как свои собственные аргументы, так и значения этих аргументов.

from icecream import ic

def foo(i):
    return i + 333

ic(foo(123))
# ic| foo(123): 456

ic(foo(123), 1 + 5);
# ic| foo(123): 456, 1 + 5: 6

# типы данных
d = {'key': {1: 'one'}}
ic(d['key'][1])
# ic| d['key'][1]: 'one'

# структуры
class klass():
    attr = 'yep'

ic(klass.attr)
# ic| klass.attr: 'yep'

Инспектирование порядка выполнения кода программы.

Для определения, какие части программы и в каком порядке выполняются, необходимо укать функцию ic(), в нужных частях кода, без аргументов. В этом случае она проверит сама себя и напечатает вызывающее имя файла, номер строки и родительскую функцию.

from icecream import ic

def foo():
    ic()
    first()

    if expression:
        ic()
        second()
    else:
        ic()
        third()

# ic| test.py:4 in foo()
# ic| test.py:11 in foo()

Другие варианты использования модуля icecream.

Функция ic() возвращает переданные ей аргументы, следовательно ее можно легко вставить в уже существующий код.

from icecream import ic
a = 6

def half(i):
    return i / 2

b = half(ic(a))
# ic| a: 6
ic(b)
# ic| b: 3

Команда ic.format(*args) похож на ic(), но вывод возвращается в виде строки, а не записывается в stderr.

from icecream import ic

s = 'sup'
out = ic.format(s)

print(out)
# ic| s: 'sup'

Включение/выключение вывода модуля icecream.

Вывод функции ic() можно полностью отключить, а позже снова включить с помощью ic.disable() и ic.enable() соответственно. При отключенном выводе, функция ic(), конечно же, продолжит возвращать переданные ей аргументы.

from icecream import ic
ic(1)

ic.disable()
ic(2)

ic.enable()
ic(3)

# ic| 1: 1
# ic| 3: 3

Доступ к модулю icecream из импортируемых файлов.

Чтобы сделать ic() доступным в каждом файле программы без необходимости импорта его в каждый файл, то нужно вызвать функцию install() в корневом файле. Например:

# запускаемый файл `a.py`
from icecream import install
install()

from B import foo
foo()

А затем в файле b.py, который импортируется в a.py, просто будем вызывать функцию ic():

# импортируемый файл `b.py`
def foo():
    x = 3
    ic(x)

Функция install() добавляет ic() во встроенный модуль builtins, который используется всеми файлами, импортированными интерпретатором. Точно так же ic() позже можно удалить функцией uninstall().

Функция ic() также может быть импортирована способом, который не завершается сбоем, если модуль icecream не установлен, например, на боевых серверах. С этой целью можно использовать следующий фрагмент импорта:

try:
    from icecream import ic
except ImportError: 
    # откат, если модуль `icecream` не установлен.
    ic = lambda *a: None if not a else (a[0] if len(a) == 1 else a)

Настройка модуля icecream.

Для настройки вывода icecream можно использовать ic.configureOutput(). Вот ее синтаксис:

ic.configureOutput(prefix='ic |', 
                   outputFunction=sys.stderr, 
                   argToStringFunction=pprint.pformat, 
                   includeContext=False)

Она поможет изменить следующие настройки по умолчанию:

  • префикс вывода (по умолчанию ic |),
  • функцию, отвечающую за вывод (по умолчанию sys.stderr),
  • способ сериализации аргументов в строки,
  • включить контекст вызова ic() (имя файла, номер строки и родительскую функцию) в ее вывод.

Аргумент prefix.

Аргумент prefix отвечает за изменение префикса, при выводе отладочной информации.

from icecream import ic
ic.configureOutput(prefix='hello => ')
ic('world')
# hello => 'world'

Значение аргумента prefix также может быть функцией.

import time
from icecream import ic

def unixTimestamp():
    return f'{int(time.time())} |'

ic.configureOutput(prefix=unixTimestamp)
ic('world')
1519185860 | 'world': 'world'

Аргумент outputFunction.

Аргумент outputFunction, должен быть функцией, в которую будет передан вывод ic(). По умолчанию используется sys.stderr.

import logging
from icecream import ic

def warn(s):
    logging.warning(s)

ic.configureOutput(outputFunction=warn)
ic('eep')
# WARNING:root:ic| 'eep': 'eep'

Аргумент argToStringFunction.

Аргумент outputFunction, должен быть функцией, которая будет вызвана со значениями аргументов, передаваемых ic() и которые должны быть сериализованы в отображаемые строки. По умолчанию используется функция pprint.pformat(), но ее можно изменить, например, для обработки нестандартных типов данных индивидуальным образом.

from icecream import ic

def toString(obj):
    if isinstance(obj, str):
        return f'[!string "{obj}" with length {len(obj)}!]'
    return repr(obj)

ic.configureOutput(argToStringFunction=toString)
ic(7, 'hello')
# ic| 7, [!string "hello" with length 5!]

Аргумент includeContext.

Если аргумент IncludeContext=True, то в вывод добавляется: имя файла, родительская функция и номер строки, в которой вызвана ic().

from icecream import ic
ic.configureOutput(includeContext=True)

def foo():
    ic('str')

foo()
# ic| test.py:5 in foo()- 'str'