Кот-наставник— Привет! Сейчас разберём тему, из-за которой новички чаще всего бросают обучение: ошибки.
— Спойлер:
tracebackне ругается, а рассказывает, где искать. Ошибка — это данные, а не приговор. Разберёмся, как её читать и обрабатывать.
Ошибки и исключения в Python — это не сбой системы, а встроенный механизм обратной связи. Когда программа делает то, что Python выполнить не может — делит на ноль или ищет несуществующее имя, — интерпретатор останавливается и показывает traceback: тип ошибки, сообщение и цепочку строк, по которой до неё дошло.
В этом гайде научимся читать traceback, перехватывать ошибки через try, except, else и finally, поднимать собственные через raise и отличать честную обработку от маскировки дефекта. Все примеры запускаются в Python 3.12.
Ошибки в Python: коротко
Ошибка останавливает программу и показывает traceback. Последняя строка traceback — тип и причина, строки выше — путь выполнения до неё. Перехватывают ошибку конструкцией try/except, причём перехватывать нужно конкретный тип. Минимальный пример:
try:
print(10 / 0)
except ZeroDivisionError:
print("На ноль делить нельзя")На ноль делить нельзя
Что такое исключение в Python?
Исключение (exception) — сигнал о том, что выполнение пошло по пути, который Python завершить не может: деление на ноль, обращение к несуществующему ключу, чтение несуществующего файла.
Когда возникает исключение, Python «поднимает» его (в терминах языка —
raise), и оно распространяется вверх по цепочке вызовов, пока его кто-то не перехватит (except) или пока программа не остановится, показав traceback — отчёт о том, что произошло.
Термины «ошибка» и «исключение» в Python почти синонимы: все встроенные ошибки — NameError, ValueError, KeyError и остальные — это классы исключений. Разница скорее в оттенке: «ошибка» — это ситуация, «исключение» — механизм, которым Python о ней сообщает.
Ключевая мысль для новичка: падение программы — это не «Python сломался». Это Python говорит: «я дошёл до места, где мои правила не позволяют продолжить, — вот тип проблемы и вот где это случилось».
Как читать traceback?
Traceback — главный инструмент отладки, и читается он не так, как обычный текст. Разберём на программе, которая считает среднюю цену:
def average(total, count):
"""Делит total на count."""
return total / count
def report(prices):
return average(sum(prices), len(prices))
print(report([]))Ошибка выполнения — main.py, строка 3
ZeroDivisionError: division by zero
Читай traceback снизу вверх. Последняя строка — самая важная: ZeroDivisionError: division by zero означает «ошибка деления на ноль». Строки выше — цепочка вызовов: выполнение началось со строки 8, зашло в report на строке 6, оттуда в average на строке 3, где и случилось деление на ноль. Номера строк указывают, куда смотреть в коде.
В примере список prices пуст, поэтому sum(prices) равен нулю, а len(prices) — тоже нулю: деление 0 / 0 и даёт ошибку. Traceback не предлагает решение — он точно сообщает факты, а причину находишь уже ты.
Разбери traceback по строкам
Кликни по строке отчёта — блок покажет строку кода, на которую она указывает, и пояснение.
def average(total, count):
"""Делит total на count."""
return total / count
def report(prices):
return average(sum(prices), len(prices))
print(report([]))Traceback (most recent call last):
File "main.py", line 8, in <module>
print(report([]))
File "main.py", line 6, in report
return average(sum(prices), len(prices))
File "main.py", line 3, in average
return total / count
ZeroDivisionError: division by zeroСтарт программы
File "main.py", line 8, in <module>print(report([]))С этой строки всё началось: программа вызвала report([]) с пустым списком. Верхние строки traceback — это путь выполнения: ошибки тут ещё нет.
Проход через report()
File "main.py", line 6, in reportreturn average(sum(prices), len(prices))Выполнение зашло в report: список prices пуст, поэтому sum(prices) = 0 и len(prices) = 0 — вызывается average(0, 0). Строка ещё не завершилась: ждёт результата.
Место падения
File "main.py", line 3, in averagereturn total / countЗдесь всё и остановилось: total / count превратилось в 0 / 0. Деление на ноль — операция, которую Python выполнить не может.
Тип и причина
ZeroDivisionError: division by zeroПоследняя строка — самое главное: тип ошибки ZeroDivisionError и причина division by zero. Начинай диагностику отсюда и читай traceback снизу вверх.
Последняя строка traceback — тип ошибки и причина, строки выше — цепочка вызовов до неё. Читай снизу вверх: сначала тип и причина, затем место падения, затем путь выполнения.
Частые ошибки новичка и что они означают
Каждый тип ошибки — это отдельная ситуация со своей причиной. Вот самые частые на старте:
| Ошибка | Что означает | Где разобрано |
|---|---|---|
NameError | Имя переменной или функции не найдено | NameError: как исправить |
KeyError | В словаре нет такого ключа | KeyError в Python: как исправить |
ValueError | Тип подходящий, но значение не то | ValueError: invalid literal for int() — как исправить |
TypeError | Не подходит сам тип значения | TypeError: can only concatenate str |
IndexError | Номер за пределами списка или строки | IndexError: list index out of range |
IndentationError | Неверные отступы | IndentationError: unexpected indent |
UnboundLocalError | Локальная переменная прочитана до присваивания | UnboundLocalError: причина и как исправить |
ZeroDivisionError | Деление на ноль | Разобрана в этом гайде ниже |
Обрати внимание: ошибки различаются не по «серьёзности», а по ситуации. Одна и та же строка print(data[5]) даст IndexError для списка из трёх элементов, KeyError — если data словарь без ключа 5, и TypeError — если data вообще не поддерживает доступ по индексу. Точный тип ошибки — половина диагностики.
try и except: перехватываем ошибку
Падение не всегда уместно. Если ошибка — предусмотренная часть работы программы (пользователь ввёл не число, файла может не быть), её перехватывают и обрабатывают. Конструкция такая:
def average(total, count):
return total / count
prices = []
try:
print(average(sum(prices), len(prices)))
except ZeroDivisionError:
print("Список пуст — среднее не посчитать")Список пуст — среднее не посчитать
В блоке try находится код, который может упасть. Если внутри возникает ZeroDivisionError, выполнение переходит в блок except с тем же типом, и программа продолжает жить. Если бы ошибка была другого типа, например NameError, этот except её бы не поймал — и программа упала бы как обычно.
Важно: в try клади только то, что действительно может упасть. Если обернуть в try весь файл, станет непонятно, какая именно строка и с какой ошибкой защищена.
Перехватывай конкретный тип, а не всё подряд
Для разных ситуаций можно написать несколько блоков except — по одному на тип ошибки:
try:
value = int(input("Введи число: "))
print(100 / value)
except ValueError:
print("Это не число")
except ZeroDivisionError:
print("На ноль делить нельзя")Это не число
Ввод пользователя: 0
На ноль делить нельзяСинтаксическая ошибка — main.py, строка 1
SyntaxError: invalid syntax (строка 1)
Неверный ввод даёт ValueError, ноль — ZeroDivisionError, и каждый обрабатывается своим способом. Если бы подошли оба типа, их можно перечислить в одном блоке через запятую: except (ValueError, ZeroDivisionError):.
else: код, который выполняется только при успехе
Блок else относится не к if, а к try: он выполняется, только если ошибки не было. В него кладут действия, которые нужны лишь при успешном выполнении защищённого кода:
try:
number = int(input("Введи число: "))
except ValueError:
print("Это не число")
else:
print("Удвоенное число:", number * 2)Удвоенное число: 50
Ввод пользователя: двадцать
Это не числоСинтаксическая ошибка — main.py, строка 1
SyntaxError: invalid syntax (строка 1)
Зачем else, если можно просто продолжить код после try/except? Затем, что else отделяет успешную ветку от защищаемой операции. Код после всей конструкции выполнится и при ошибке, а код в else — нет. Так читатель сразу видит: вот опасная операция, вот её обработка, а вот то, что делаем только при успехе.
finally: код, который выполняется всегда
Блок finally выполняется в любом случае: была ошибка или нет. Его используют для обязательной уборки — закрыть файл, завершить соединение, освободить ресурс:
try:
result = 100 / int(input("Введи число: "))
except (ValueError, ZeroDivisionError):
print("Что-то пошло не так")
else:
print("Результат:", result)
finally:
print("Попытка завершена")Что-то пошло не так Попытка завершена
Сначала отрабатывает except или else, затем — всегда — finally. Даже если бы ошибка вообще не была перехвачена и программа упала, finally всё равно выполнился бы: Python запускает его перед тем, как исключение уйдёт дальше вверх.
raise: поднимаем ошибку сами
Обработка ошибок — это не только их перехват. Иногда правила задаёт твоя функция, и неверные данные — это ошибка, которую должна увидеть вызывающая сторона. Тогда исключение поднимают явно через raise:
def apply_discount(price, percent):
if not 0 <= percent <= 100:
raise ValueError("Процент скидки должен быть от 0 до 100")
return price * (100 - percent) / 100
print(apply_discount(1000, 150))Ошибка выполнения — main.py, строка 3
ValueError: Процент скидки должен быть от 0 до 100
Функция сама проверяет контракт: процент вне диапазона — это ошибка вызывающего кода, и прятать её нельзя. Сообщение после raise — это текст для человека, который будет читать traceback, поэтому пиши его конкретно: что не так и какой диапазон допустим.
Такой подход честнее, чем тихо «чинить» данные внутри функции: если apply_discount сам подрезал бы процент до 100, вызывающий код никогда не узнал бы о своей ошибке.
Иерархия исключений
Все встроенные ошибки Python — классы, выстроенные в иерархию. NameError, ValueError, KeyError и остальные наследуются от общего родителя Exception. Отсюда два практических правила:
except Exceptionперехватывает почти все встроенные ошибки — слишком широко для обычной программы.exceptбез типа перехватывает ещё и служебные сигналы вродеKeyboardInterrupt(Ctrl+C) — почти никогда не то, что нужно.
Поэтому в повседневном коде перехватывают конкретный тип: except ValueError, except KeyError, except (ValueError, ZeroDivisionError).
Частые ошибки при обработке ошибок
- Глотание ошибки:
except ValueError: pass— код продолжает работу с неверными данными, причина исчезла навсегда. - Перехват до диагноза: сначала понять, почему падает, потом оборачивать в
try— иначе защищаешь не ту операцию. - Слишком широкий `try`: один блок на сто строк ловит ошибки неясного происхождения.
- Маскировка дефекта: перехватывать
NameErrorот опечатки вместо исправления имени. - Обработчик без сообщения:
except ValueError: print("Ошибка")не говорит ни что случилось, ни что делать.
Общий принцип: обработчик отвечает на предусмотренную ситуацию — неверный ввод, отсутствующий файл, сбой сети. Дефект собственного кода — опечатку, неверный порядок строк, забытое присваивание — нужно исправлять, а не перехватывать. Это и есть разница между «ментором, который объясняет причину» и «линтером, который прячет сообщение».
Короткие ответы про ошибки в Python
Что такое исключение в Python?
Сигнал о ситуации, которую Python не может выполнить: деление на ноль, несуществующий ключ или файл. Исключение можно перехватить через try/except или дать программе упасть с traceback.
Как читать traceback?
Снизу вверх. Последняя строка — тип ошибки и сообщение, строки выше — цепочка вызовов с номерами строк, по которой выполнение дошло до ошибки.
В чём разница между ошибкой и исключением?
В Python эти слова почти синонимы: все встроенные ошибки — это классы исключений. «Ошибка» описывает ситуацию, «исключение» — механизм, которым Python о ней сообщает.
Когда нужен else в try?
Когда действие нужно выполнить только при успехе защищённого кода. Если поставить его просто после конструкции, оно выполнится и после перехваченной ошибки.
Что делает finally?
Выполняется всегда — и при успехе, и при ошибке, и даже при необработанном падении. Нужен для обязательной уборки: закрыть файл, освободить ресурс.
Почему не стоит перехватывать все ошибки подряд?
except без типа ловит всё, включая опечатки и Ctrl+C. Программа не падает, но причина спрятана: явная ошибка превращается в тихий неверный результат.
Главное про ошибки и исключения в Python:
- Ошибка — это данные, а не приговор
Traceback точно сообщает тип ошибки, сообщение и путь выполнения. Читай его снизу вверх и начинай с последней строки.
- Перехватывай конкретный тип
except ValueError,except ZeroDivisionError, а не всё подряд. Несколько типов — в скобках через запятую. - Конструкция — это четыре блока
tryзащищает,exceptобрабатывает,elseвыполняется только при успехе,finally— всегда. - Свои правила — через
raiseФункция поднимает исключение, когда входные данные нарушают её контракт, и сообщает причину человеку.
- Обработчик — для предусмотренных ситуаций
Неверный ввод, отсутствующий файл, сбой сети — обрабатывай. Опечатку и неверный порядок строк — исправляй.
Кот-наставник— Запомни порядок: сначала прочитай последнюю строку traceback, потом реши, эта ошибка — часть жизни программы или дефект кода. Первую обрабатывай через
try, второй — чини источник. Тогда ошибки перестанут пугать и начнут экономить время.
Научись читать ошибки, а не бояться их
На SkillPy ты пишешь код сам, а AI-наставник разбирает именно твою ошибку: объясняет traceback, причину и следующий шаг. Ошибка превращается в подсказку. Первые задачи доступны бесплатно.
