Забудьте об использовании print() для отладки в Python

В этой статье мы разберем более совершенное средство дебага — модуль icecream.

icecream

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

Конечно, полностью функцию print() ничто не заменит (да и зачем?). Но для облегчения отладки существует множество интересных решений. В этой статье мы познакомимся с библиотекой “Ice Cream”. Она позволит вам значительно упростить и ускорить процесс дебага.

Плохой пример

Давайте рассмотрим действительно плохой пример. Допустим, мы объявили функцию и хотим проверить, хорошо ли она работает. 

def square_of(num):
    return num * num

Эта функция просто возвращает квадрат числа, переданного в качестве аргумента. Проверить ее работу мы можем так:

print(square_of(2))
print(square_of(3))
print(square_of(4))

# Вывод:
4
9
16

Кажется, все работает. Но на практике у нас будет куда больше строк кода. Кроме того, в коде может быть много функций print(), выводящих разные вещи в область output-а. В в таких условиях можно не сразу понять, какой именно print() вывел ту или иную строку в консоли. 

Хороший тон — добавить небольшое описание в содержимое print(), чтобы видеть, какую именно информацию он выводит.

print('Квадрат 2:', square_of(2))
print('Квадрат 3:', square_of(3))
print('Квадрат 4:', square_of(4))

# Вывод:
Квадрат 2: 4
Квадрат 3: 9
Квадрат 4: 16

Выглядит гораздо лучше. Но согласитесь, прописывать все это каждый раз довольно утомительно. К тому же, после дебага нам скорее всего придется все эти print-ы удалять.

Исследование переменных с помощью функции ic() из библиотеки Ice Cream

А теперь рассмотрим библиотеку Ice Cream. Как она решает проблему, о которой мы говорили выше? 

В первую очередь необходимо установить эту библиотеку с помощью pip.

pip install icecream

Затем ее нужно импортировать.

from icecream import ic

Теперь мы можем использовать эту библиотеку для вывода нужной информации.

Вызов функции

Мы можем использовать Ice Cream для вывода результата функции точно так же, как и print().

ic(square_of(2))
ic(square_of(3))
ic(square_of(4))

# Вывод:
ic| square_of(2): 4
ic| square_of(3): 9
ic| square_of(4): 16

Отлично! Мы не указали в функции ic() ровным счетом ничего, но в выводе у нас и имя функции, и аргумент. То есть, нам не нужно вручную добавлять краткое описание!

Доступ к элементу словаря

Ice Cream выводит все многословно, с подробностями, что полезно при отладке. Это касается не только функций, но и объектов вроде словарей — например, пар «ключ-значение».

my_dict = {
    'имя': 'Артем',
    'возраст': 33
}
ic(my_dict['имя'])

# Вывод:
ic| my_dict['имя']: 'Артем'

В этом примере мы объявили словарь и попытались получить доступ к значению с помощью ключа. Вывод Ice Cream содержит и имя словаря, и значение, и сам ключ. 

Доступ к атрибутам объекта

Еще один пример. Объявим класс и создадим его объект.

class Dog():
    num_legs = 4
    tail = True
dog = Dog()

А теперь воспользуемся Ice Cream, чтобы вывести его атрибут.

ic(dog.tail)

# Вывод:
ic| dog.tail: True

Дебаг блока if-else

Библиотека Ice Cream полезна не только при работе с переменными, но и в условном ветвлении — if-else блоке. Давайте напишем небольшой блок кода с if-else.

input = 'Иван'
if input == 'Иван':
    ic()
else:
    ic()

Мы просто вставили функцию Ice Cream в блоки if и else — посмотрим, что из этого выйдет.

ic| main.py:13 in <module> at 00:43:18.803

Хоть мы и не написали никаких действий в if-else, функция ic() сообщает нам, где и когда она была вызвана, а также номер строки.

Рассмотрим еще один пример, более практичный:

def check_user(username):
    if username == 'Максим':
        # сделать что-то
        ic()
    else:
        # сделать что-то
        ic()
check_user('Максим')
check_user('Денис')

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

ic| main.py:14 in check_user() at 00:46:13.157
ic| main.py:17 in check_user() at 00:46:13.158

Вставка в уже существующий код

Эту фишку Ice Cream, пожалуй, следует выделить. Функция ic() не только выводит подробную информацию, в нее можно передавать значения. Таким образом, она может быть и оберткой для чего-нибудь. То есть, мы можем вставить функцию ic() в любую часть нашего кода, не изменив его функционал. 

Вернемся к функции sqaure_of(), которую определили ранее.

Допустим, у нас есть переменная num и мы хотим вычислить ее квадрат. Вместо num в square_of(num) мы можем вставить функцию ic() с num внутри.

num = 2
square_of_num = square_of(ic(num))

# Вывод:
ic| num: 2

Как видите, в консоль выводится значение num, но результат, присвоенный square_of_num, не изменяется.

Мы можем это проверить:

if ic(square_of_num) == pow(num, 2):
    ic('Правильно!')

# Вывод:
ic| square_of_num: 4
ic| 'Правильно!'

То есть, square_of_num равен квадрату num. Функция ic(), которую мы поместили в if-блок, никак не влияет на результат, но выводит значение функции, а это поможет при отладке!

Отключение Ice Cream

Одна из самых больших проблем использования print() для отладки — огромное количество строк с этой функцией. Обычно, когда мы заканчиваем написание программы, весь наш код захламлен print-ами. А удаление всех строк кода с print() — задачка не из приятных. 

Но если для отладки мы используем Ice Cream, нам не нужно удалять строки, где она использовалась: достаточно лишь отключить эту библиотеку.

ic.disable()

После этого функция ic() перестанет выводить что-либо в консоль. В примере ниже в консоль ничего не выводится.

if ic(square_of_num) == pow(num, 2):
    ic('Правильно!')

Возможно, вам стало интересно — а что же с переменной square_of_num? Будет ли она работать, если мы отключим Ice Cream? Не переживайте, отключение библиотеки лишь прекращает вывод в консоль — все остальное работает!

Если мы заменим функцию вывода на print(), вывод в консоль продолжится. Это значит, что ic(square_of_num) эквивалентно square_of_num.

from icecream import ic

def square_of(num):
    return num*num

ic.disable()

num = 2
square_of_num = square_of(ic(num))

if ic(square_of_num) == pow(num, 2):
    print('Правильно')

# Вывод:
Правильно

Если мы захотим вернуться к дебагу, мы можем запустить Ice Cream снова.

ic.enable()

Настройка вывода

Вывод Ice Cream мы можем настроить. Обычно нам нужно лишь изменить префикс ic — |. Да, его можно изменить! Например, на Debug |. Благодаря этому любой человек поймет, что эти данные нужны для дебага. 

ic.configureOutput(prefix='Debug | ')
ic('Тест')

# Вывод:
Debug | 'Тест'

Префикс можно заменить не на статическую строку, а на функцию. Например, объявим функцию, возвращающую текущее время в виде отформатированной строки:

from datetime import datetime
def now():
    return f'[{datetime.now()}] '

Теперь мы можем использовать эту функцию в качестве префикса Ice Cream!

ic.configureOutput(prefix=now)
ic('Тест')

# Вывод:
[2021-06-28 01:00:30.240427] 'Тест'

Итоги

Мы познакомились с прекрасной сторонней библиотекой Python Ice Cream. Поведение функции ic(), которую мы получаем в этой библиотеке, — улучшенный аналог поведения print(). Благодаря более подробному выводу эта функция очень полезна при отладке программ.

Разумеется, библиотека Ice Cream не заменит функцию print(). Прежде всего, потому что она была создана с прицелом именно на отладку. Также она не заменит систему логов. Но попробовать воспользоваться ею однозначно стоит!

Прокрутить вверх