Евгений Левада 10 ноября 2020

🐍 Найдите и исправьте ошибки в коде на Python: отладка с IDLE

Все допускают ошибки – даже опытные профессиональные разработчики, а Python и IDLE отлично их улавливают. Давайте разберемся, как это работает.

Перевод публикуется с сокращениями, автор оригинальной статьи David Amos.

Выявление ошибок называется дебаггингом, а дебаггер – помогающий понять причину их появления инструмент. Умение находить и исправлять ошибки в коде – важный навык в работе программиста, не пренебрегайте им.

IDLE (Integrated Development and Learning Environment) – кроссплатформенная интегрированная среда разработки и обучения для Python, созданная Гвидо ван Россумом.

Используйте окно управления отладкой

Основным интерфейсом отладки в IDLE является специальное окно управления (Debug Control window). Открыть его можно, выбрав в меню интерактивного окна пункт Debug→Debugger.

Примечание: если отладка отсутствует в строке меню, убедитесь, что интерактивное окно находится в фокусе.

Всякий раз, когда окно отладки открыто, интерактивное окно отображает [DEBUG ON].

Обзор окна управления отладкой

Чтобы увидеть работу отладчика, напишем простую программу без ошибок. Введите в редактор следующий код:

        for i in range(1, 4):
    j = i * 2
    print(f"i is {i} and j is {j}")
    

Сохраните все, откройте окно отладки и нажмите клавишу F5 – выполнение не завершилось.

Окно отладки будет выглядеть следующим образом:

Обратите внимание, что панель в верхней части окна содержит сообщение:

        > '__main__'.<module>(), line 1: for i in range(1, 4):
    

Расшифруем: код for i in range(1, 4): еще не запущен, а '__main__'.module() сообщает, что в данный момент мы находимся в основном разделе программы, а не в определении функции.

Ниже панели стека находится панель Locals, в которой перечислены непонятные вещи: __annotations__, __builtins__, __doc__ и т. д. – это внутренние системные переменные, которые пока можно игнорировать. По мере выполнения программы переменные, объявленные в коде и отображаемые в этом окне, помогут в отслеживании их значений.

В левом верхнем углу окна расположены пять кнопок: Go, Step, Over, Out и Quit – они управляют перемещением отладчика по коду.

В следующих разделах вы узнаете, что делает каждая из этих кнопок.

Кнопка Step

Нажмите Step и окно отладки будет выглядеть следующим образом:

Обратите внимание на два отличия. Во-первых, сообщение на панели стека изменилось:

        > '__main__'.<module>(), line 2: j = i * 2:
    

На этом этапе выполняется line 1 и отладчик останавливается перед выполнением line 2.

Во-вторых – новая переменная i со значением 1 на панели Locals. Цикл for в line 1 создал переменную и присвоил ей это значение.

Продолжайте нажимать кнопку Step, чтобы пройтись по коду строка за строкой, и наблюдайте, что происходит в окне отладчика. Когда доберетесь до строки print(f"i is {i} and j is {j}"), сможете увидеть вывод, отображаемый в интерактивном окне по одному фрагменту за раз.

Здесь важно, что можно отслеживать растущие значения i и j по мере прохождения цикла for. Это полезная фича поиска источника ошибок в коде. Знание значения каждой переменной в каждой строке кода может помочь точно определить проблемную зону.

Точки останова и кнопка Go

Часто вам известно, что ошибка должна всплыть в определенном куске кода, но неизвестно, где именно. Чтобы не нажимать кнопку Step весь день, установите точку останова, которая скажет отладчику запускать весь код, пока он ее не достигнет.

Точки останова сообщают отладчику, когда следует приостановить выполнение кода, чтобы вы могли взглянуть на текущее состояние программы.

Чтобы установить точку останова, щелкните правой кнопкой мыши (Ctrl для Mac) по строке кода, на которой хотите сделать паузу, и выберите пункт Set Breakpoint – IDLE выделит линию желтым. Чтобы удалить ее, выберите Clear Breakpoint.

Установите точку останова в строке с оператором print(). Окно редактора должно выглядеть так:

Сохраните и запустите. Как и раньше, панель стека указывает, что отладчик запущен и ожидает выполнения line 1. Нажмите кнопку Go и посмотрите, что произойдет:

Теперь на панели стека информация о выполнении line 3:

        > '__main__'.<module>(), line 3: print(f"i is {i} and j is {j}")
    

На панели Locals мы видим, что переменные i и j имеют значения 1 и 2 соответственно. Нажмем кнопку Go и попросим отладчик запускать код до точки останова или до конца программы. Снова нажмите Go – окно отладки теперь выглядит так:

На панели стека отображается то же сообщение, что и раньше – отладчик ожидает выполнения line 3. Однако значения переменных i и j теперь равны 2 и 4. Интерактивное окно также отображает выходные данные после первого запуска строки с помощью функции print() через цикл.

Нажмите кнопку в третий раз. Теперь i и j равны 3 и 6. Если нажать Go еще раз, программа завершит работу.

Over и Out

Кнопка Over работает, как сочетание Step и Go – она перешагивает через функцию или цикл. Другими словами, если вы собираетесь попасть в функцию с помощью отладчика, можно и не запускать код этой функции – кнопка Over приведет непосредственно к результату ее выполнения.

Аналогично если вы уже находитесь внутри функции или цикла – кнопка Out выполняет оставшийся код внутри тела функции или цикла, а затем останавливается.

В следующем разделе мы изучим некоторые ошибки и узнаем, как их исправить с помощью IDLE.

Борьба с багами

Взглянем на «глючную» программу.

Следующий код определяет функцию add_underscores(), принимающую в качестве аргумента строковый объект и возвращающую новую строку – копию слова с каждым символом, окруженным подчеркиванием. Например, add_underscores("python") вернет «_p_y_t_h_o_n_».

Вот неработающий код:

        def add_underscores(word):
    new_word = "_"
    for i in range(len(word)):
        new_word = word[i] + "_"
    return new_word

phrase = "hello"
print(add_underscores(phrase))
    

Введите этот код в редактор, сохраните и нажмите F5. Ожидаемый результат – _h_e_l_l_o_, но вместо этого выведется o_.

Если вы нашли, в чем проблема, не исправляйте ее. Наша цель – научиться использовать для этого IDLE.

Рассмотрим 4 этапа поиска бага:

  • предположите, где может быть ошибка;
  • установите точку останова и проверьте код по строке за раз;
  • определите строку и внесите изменения;
  • повторяйте шаги 1-3, пока код не заработает.

Шаг 1: Предположение

Сначала вы не сможете точно определить местонахождение ошибки, но обычно проще логически представить, в какой раздел кода смотреть.

Обратите внимание, что программа разделена на два раздела: определение функции add_underscores() и основной блок, определяющий переменную со значением «hello» и выводящий результат.

Посмотрим на основной раздел:

        phrase = "hello"
print(add_underscores(phrase))
    

Очевидно, что здесь все хорошо и проблема должна быть в определении функции:

        def add_underscores(word):
    new_word = "_"
    for i in range(len(word)):
        new_word = word[i] + "_"
    return new_word
    

Первая строка создает переменную new_word со значением «_». Промах, проблема находится где-то в теле цикла for.

Шаг 2: точка останова

Определив, где может быть ошибка, установите точку останова в начале цикла for, чтобы проследить за происходящим внутри кода:

Запустим. Выполнение останавливается на строке с определением функции.

Нажмите кнопку Go, чтобы выполнить код до точки останова:

Код останавливается перед циклом for в функции add_underscores(). Обратите внимание, что на панели Locals отображаются две локальные переменные – word со значением «hello», и new_word со значением «_»,

Нажмите кнопку Step, чтобы войти в цикл for. Окно отладки изменится, и новая переменная i со значением 0 отобразится на панели Locals:

Переменная i – это счетчик для цикла for, который можно использовать, чтобы отслеживать активную на данный момент итерацию.

Нажмите кнопку Step еще раз и посмотрите на панель Locals – переменная new_word приняла значение «h_»:

Это неправильно т. к. сначала в new_word было значение «_», на второй итерации цикла for в ней должно быть «_h_». Если нажать Step еще несколько раз, то увидим, что в new_word попадает значение e_, затем l_ и так далее.

Шаг 3: Определение ошибки и исправление

Как мы уже выяснили – на каждой итерации цикла new_word перезаписывается следующим символом в строке «hello» и подчеркиванием. Поскольку внутри цикла есть только одна строка кода, проблема должна быть именно там:

        new_word = word[i] + "_"
    

Код указывает Python получить следующий символ word, прикрепить подчеркивание и назначить новую строку переменной new_word. Это именно то неверное поведение, которое мы наблюдали.

Чтобы все починить, нужно объединить word[i] + "_" с существующим значением new_word. Нажмите кнопку Quit в окне отладки, но не закрывайте его. Откройте окно редактора и измените строку внутри цикла for на следующую:

        new_word = new_word + word[i] + "_"
    

Примечание: Если бы вы закрыли отладчик, не нажав кнопку Quit, при повторном открытии окна отладки могла появиться ошибка:

You can only toggle the debugger when idle

Всегда нажимайте кнопку Go или Quit, когда заканчиваете отладку, иначе могут возникнуть проблемы с ее повторным запуском.

Шаг 4: повторение шагов 1-3, пока ошибка не исчезнет

Сохраните изменения в программе и запустите ее снова. В окне отладки нажмите кнопку Go, чтобы выполнить код до точки останова. Понажимайте Step несколько раз и смотрите, что происходит с переменной new_word на каждой итерации – все работает, как положено. Иногда необходимо повторять этот процесс несколько раз, прежде чем исправится ошибка.

Альтернативные способы поиска ошибок

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

Например, вместо отладки предыдущего примера можно добавить следующую строку в конец цикла for:

        print(f"i = {i}; new_word = {new_word}")
    

Измененный код будет выглядеть следующим образом:

        def add_underscores(word):
    new_word = "_"
    for i in range(len(word)):
        new_word = word[i] + "_"
        print(f"i = {i}; new_word = {new_word}")
    return new_word

phrase = "hello"
print(add_underscores(phrase))
    

Вывод должен выглядеть так:

        i = 0; new_word = h_
i = 1; new_word = e_
i = 2; new_word = l_
i = 3; new_word = l_
i = 4; new_word = o_
o_
    

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

Один из способов улучшить наш цикл – перебирать символы в word:

        def add_underscores(word):
    new_word = "_"
    for letter in word:
        new_word = new_word + letter + "_"
    return new_word
    

Заключение

Теперь вы знаете все об отладке с помощью DLE. Вы можете использовать этот принцип с различными дебагерами.

В статье мы разобрали следующие темы:

  • использование окна управления отладкой;
  • установку точки останова для глубокого понимания работы кода;
  • применение кнопок Step, Go, Over и Out;
  • четырехэтапный процессом выявления и удаления ошибок.

Не останавливайтесь в обучении и практикуйте дебаггинг – это весело!

Дополнительные материалы:

Источники

МЕРОПРИЯТИЯ

Какими средствами и приемами отладки программ вы пользуетесь?

ВАКАНСИИ

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

BUG