Урок курса

Отладка и обработка ошибок в ноутбуке

Jupyter Notebook с нуля: бесплатный курс

Ошибки и Traceback: чтение сообщений об ошибках

При разработке код часто содержит ошибки. В Jupyter, если при выполнении ячейки произошло неперехваченное исключение, вы увидите traceback – многострочное сообщение об ошибке (красного цвета).

Пример:

x = 5 
print("Результат:", x * y)

Если переменная y не определена, то при выполнении мы получим вывод:

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-7-4c2...> in <module>
      1 x = 5
----> 2 print("Результат:", x * y)

NameError: name 'y' is not defined

Разберём его:

  • Строчки с --------------------------------------------------------------------------- обрамляют traceback.

  • NameError – тип исключения.

  • Далее IPython показывает стек вызовов: здесь простой случай – прямо в модуле <module> (то есть в самой ячейке, вне функций), на строке со стрелкой произошла ошибка.

  • Стрелка ----> указывает на строку print("Результат:", x * y). Номер строки (в данном случае 2 в ячейке) и контекст.

  • Последняя строчка – описание ошибки: name 'y' is not defined.

При возникновении ошибки:

  1. Прочитайте тип ошибки и сообщение. В нашем примере NameError явно говорит – переменная y не определена. Часто распространённые ошибки:

    • NameError – опечатка или забыли определение переменной.

    • TypeError – неверный тип объекта для операции (например, вы пытаетесь сделать len(5) – 5 это int, у него нет длины).

    • IndexError – индекс вышел за границы последовательности (например, попросили 10-й элемент списка длины 5).

    • KeyError – нет такого ключа в словаре или столбца в DataFrame.

    • AttributeError – нет такого атрибута/метода у объекта (может, опечатка в названии метода).

    • ValueError – функция получила аргумент правильного типа, но некорректного значения (например, math.sqrt(-1) бросит ValueError, так как ожидалось неотрицательное).

    • IndentationError – ошибка отступов (внутри блока код отступлен неправильно).

    • SyntaxError – синтаксическая ошибка (опечатка в коде, неправильная конструкция).

  2. Просмотрите стек (Traceback). Если у вас многоуровневый вызов функций, traceback может быть длинным, указывая последовательность вызовов. Последняя (нижняя) часть – причина, выше – места, откуда эта ошибка пошла. Обычно интересует именно место в вашем коде, где проблема. Библиотечные вызовы можно сначала пропустить.

В Jupyter, к сожалению, номера строк – относительные к ячейке (в примере <ipython-input-7> – 7-я ячейка, line 2 внутри неё). Но вы видите сам код со стрелкой, этого достаточно.

После идентификации проблемы – переходим к исправлению. Иногда достаточно по сообщению понять (ах да, y не объявлен, нужно определение). Иногда сложнее – тогда переходим к отладке.

Простые методы отладки

Наиболее доступный способ – добавить выводы (print), чтобы проверить, что происходит в коде:

  • Если функция выдаёт неожиданный результат, можно внутри неё распечатать значения основных переменных на разных этапах.

  • Если цикл застрял или идёт не так, можно печатать прогресс (но осторожно с частотой, чтобы не завалить вывод).

  • Можно печатать входные аргументы функции в начале, чтобы убедиться, что она вызывается с тем, чем вы думаете.

Например, код:

def find_max(nums):
    max_val = 0
    for num in nums:
        if num > max_val:
            max_val = num
    return max_val

print(find_max([-1, -5, -3]))

Ожидаем получить -1, а функция вернёт 0 (неправильно, ведь 0 не был в списке). Почему? Потому что мы инициализировали max_val = 0, а список только отрицательный. Чтобы понять, можно добавить print:

def find_max(nums):
    max_val = 0
    for num in nums:
        print(f"Сравниваю {num} и {max_val}")
        if num > max_val:
            max_val = num
            print(f"Новый максимум: {max_val}")
    return max_val

Теперь вызов покажет:

Сравниваю -1 и 0  
(условие -1 > 0 ложно, max_val остаётся 0)  
Сравниваю -5 и 0  
...  
Сравниваю -3 и 0  
...

Видно, что проблема – начальное значение 0 не подходит для отрицательных. Решение – инициализировать max_val = float('-inf') или взять первый элемент. Так мы локализовали ошибку.

Использование нескольких ячеек для отладки: Вы можете разбить код на части и выполнять поэтапно. Например, функцию писать в одной ячейке, а вызовы – в другой. Тогда легче перезапускать только функцию, не трогая остальной код, и не перезапуская трудоёмкие шаги.

Restart Kernel & Run All при странном поведении: Иногда ошибки вызваны загрязнённым состоянием (например, у вас осталась переменная из старого запуска, и вы случайно на неё опираетесь, хотя в свежем ноутбуке её бы не было). Если ноутбук ведёт себя непредсказуемо, сделайте Kernel -> Restart & Run All. Если после этого появляются ошибки – значит, что-то не было определено или последовательность нарушена. Это поможет поймать зависимость, о которой вы могли не догадываться.

Логирование (logging): В крупных проектах вместо print часто используют модуль logging, чтобы контролировать уровень вывода (DEBUG, INFO, WARNING, ERROR). В ноутбуке для простоты можно обойтись print-ами, но если проект большой, можно настроить logging и выводить сообщения через него.

Unit-тесты в ноутбуке: Если у вас есть важные функции, стоит писать короткие тесты (assert) после них. Пример:

def add(a, b): 
    return a + b

# Тесты
assert add(2, 3) == 5
assert add("ab", "cd") == "abcd"

Если все хорошо, эти ассерты ничего не делают. Если что-то неверно, вылетит AssertionError. Это сразу сигнал разработчику.

Интерактивная отладка с %debug

IPython имеет очень удобную функцию: магия %debug. Она позволяет войти в интерактивный режим отладки после того, как произошла ошибка.

Допустим, другая функция:

def divide(a, b):
    result = a / b
    return result

x = 10
y = 0
divide(x, y)

При выполнении возникнет ZeroDivisionError. Traceback укажет на строку result = a / b. Чтобы исследовать состояние программы в момент ошибки, сразу после появления traceback в той же среде выполните:

%debug

Вы попадёте в встроенный отладчик pdb, в контекст той строки, где случилась ошибка. В Jupyter это происходит прямо в ячейке (появится промпт ipdb>). Вы можете:

  • Посмотреть значения переменных: напишите a и Enter (выведет 10), b (выведет 0).

  • Понять причину: явно b=0, деление на ноль.

  • В pdb доступны команды: l (list код вокруг текущей позиции), u (up) подняться на уровень выше (вызвавшая функция), d (down) спуститься обратно, q (quit) выйти из отладчика.

В данном примере код простой, но представьте, ошибка в глубине вызовов – %debug переносит вас прямо туда. Вы можете исследовать локальные переменные, шагать по коду (команды n, s – next, step), но чаще в post-mortem режиме (после упавшей ошибки) вы просто осматриваетесь.

Полезно: %debug можно вставлять и в код, обернув подозрительный участок в try/except:

python

КопироватьРедактировать

try: complicated_function(data) except Exception as e: print("Ошибка:", e) %debug

Это поймает любую ошибку в complicated_function и сразу запустит отладчик на месте.

Чтобы автоматически входить в отладчик при любой ошибке, можно выполнить %pdb on (Persistent Debug). Тогда при каждой ошибке будет как будто автоматически вызываться %debug. %pdb off отключает.

JupyterLab Debugger: В JupyterLab, если вы используете ядро IPykernel 5.3+ и JupyterLab 3+, есть графический отладчик (с брейкпоинтами). Нужно в Notebook включить режим debug (иконка жука). Это удобнее для сложной отладки: можно щёлкать брейкпоинты на строках, запускать ячейку – выполнение остановится на брейкпоинте, и в sidebar Lab вы увидите стек, переменные, и сможете шагать по коду. Имейте в виду, не все ядра поддерживают (для Python используйте ipykernel, а не, например, IRkernel).

Совместное использование с IDE: Иногда разработчики пишут код в ноутбуке, а отлаживают в IDE. Можно, например, вставить import pdb; pdb.set_trace() – это встроенный вызов отладчика pdb, он тоже откроет интерактивную отладку (но в консоли, что в Jupyter выглядит так же, как %debug). Однако лучше использовать %debug прямо.

Обработка исключений в коде: В ноутбуке, помимо отладки, можно сами ошибки обрабатывать:

try:
    complicated_function(data)
except Exception as e:
    print("Ошибка:", e)
    %debug

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

Резюме: Отладка – неотъемлемая часть разработки. В Jupyter у вас сочетание: прочитать traceback, добавить print/логов, использовать %debug для глубокого копания. Такой цикл позволяет исправлять ошибки очень быстро. Важно писать код небольшими кусочками и тестировать их в ноутбуке, тогда вы ловите ошибки рано и с меньшим контекстом.

На будущее: Чем опытнее вы становитесь, тем больше будете предусматривать типичные ошибки (и, например, писать проверки if b == 0: ...). А инструменты Jupyter всегда под рукой для поиска причины неожиданных ситуаций.

Полезный совет: храните ноутбук в чистом состоянии – периодически делайте Restart & Run All. Если после этого всё работает – ваш код самодостаточен и последовательно. Это защищает от ситуаций, когда "работает, пока не перезапустишь kernel" из-за скрытых зависимостей.

Попробуйте решить

Какая магическая команда отображает список всех переменных с типами и размерами?

​​​​​​​

Продолжить с проверкой и прогрессом

Откройте интерактивный раннер с заданиями урока.

Перейти к интерактивному уроку
Отладка кода в Jupyter Notebook: ошибки и debugger