Что такое исключения
Исключение (exception) — это событие, которое возникает во время выполнения программы и сигнализирует об ошибочной ситуации. В Python исключение — это объект, содержащий:
- тип — класс исключения, например
ValueError; - сообщение — описание проблемы;
- traceback — информацию о том, где именно произошла ошибка.
Если исключение не обработано, интерпретатор останавливает программу и выводит traceback:
# zero_division_demo.py
print(10 / 0) # ZeroDivisionError: division by zero
Обработка исключений позволяет заменить аварийное завершение на понятное сообщение и продолжить выполнение.
Частые встроенные исключения
| Исключение | Причина | Пример |
|---|---|---|
ZeroDivisionError |
Деление на ноль | 10 / 0 |
ValueError |
Некорректное значение | int("abc") |
KeyError |
Несуществующий ключ словаря | info = {"a": 1}; info["b"] |
IndexError |
Индекс за пределами последовательности | [1, 2][10] |
TypeError |
Операция для неподходящего типа | "2" + 2 |
FileNotFoundError |
Файл не найден | open("missing.txt") |
Иерархия исключений
Все исключения в Python наследуются от BaseException. Однако для обработки ошибок обычно используют ветвь Exception:
BaseException
├── SystemExit # выход из программы
├── KeyboardInterrupt # Ctrl+C
├── GeneratorExit # закрытие генератора
└── Exception # базовый класс для ошибок
├── ArithmeticError
│ └── ZeroDivisionError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── ValueError
├── TypeError
└── ...
Общий обработчик except Exception: перехватывает все ошибки из ветви Exception, но не прерывает программу при Ctrl+C или sys.exit().
Конструкция try/except
Блок try содержит код, который может вызвать исключение. Блок except определяет, что делать, если возникла ошибка указанного типа:
# safe_divide.py
try:
result = 10 / 0
except ZeroDivisionError:
print("Ошибка: деление на ноль!")
print("Программа продолжает работу")
Важные правила:
- В блок
tryвключают только код, который может вызвать исключение. Это упрощает отладку. - Обработчики проверяются сверху вниз. Первый подходящий сработает, остальные будут пропущены.
- Общий
except Exception:ставят последним, иначе он перехватит всё и более специфичные обработчики не сработают.
Несколько исключений
Можно задать несколько обработчиков или поймать несколько типов в одном блоке через кортеж:
# multiple_exceptions.py
while True:
try:
value = int(input("Введите ненулевое число: "))
result = 10 / value
print(f"Результат: {result}")
break
except (ZeroDivisionError, ValueError):
print("Ошибка! Введите корректное ненулевое число.")
Для получения текста ошибки используют конструкцию as e:
# exception_message.py
try:
number = int("abc")
except ValueError as e:
print(f"Произошла ошибка: {e}")
Блоки else и finally
else выполняется, если в try не возникло исключения. Он помогает отделить «опасный» код от основной логики:
# try_else.py
try:
number = int(input("Введите число: "))
except ValueError:
print("Ошибка! Введите корректное число.")
else:
print(f"Вы ввели число: {number}")
finally выполняется в любом случае — независимо от того, было исключение или нет. Его используют для освобождения ресурсов:
# try_finally.py
try:
number = int(input("Введите число: "))
result = 10 / number
except (ValueError, ZeroDivisionError):
print("Ошибка ввода или деления.")
finally:
print("Завершение программы.")
Полная конструкция выглядит так:
# full_structure.py
try:
number = int(input("Введите число: "))
result = 10 / number
except ValueError:
print("Некорректное значение.")
except ZeroDivisionError:
print("Деление на ноль.")
else:
print(f"Результат: {result}")
finally:
print("Программа завершена.")
Возбуждение исключений через raise
Ключевое слово raise позволяет программисту самому создать исключение. Это полезно, когда внешние данные нарушают бизнес-правила:
# raise_demo.py
age = int(input("Введите возраст: "))
if age < 18:
raise ValueError("Возраст должен быть 18 лет и старше.")
После raise программа останавливается, если исключение не перехвачено:
# raise_with_except.py
while True:
try:
age = int(input("Введите возраст: "))
if age < 18:
raise ValueError("Возраст должен быть 18 лет и старше.")
print("Возраст принят.")
break
except ValueError as e:
print(f"Ошибка: {e}. Попробуйте снова.")
Пользовательские исключения
Собственные исключения создаются наследованием от Exception (или его подкласса). Это делает ошибки программы более понятными и позволяет отличать их от встроенных:
# custom_exception.py
class AgeValidationError(Exception):
"""Ошибка: пользователь слишком молод."""
def __init__(self, age: int, min_age: int):
self.age = age
self.min_age = min_age
super().__init__(f"Возраст {age} меньше минимального {min_age}")
def check_age(age: int) -> None:
if age < 18:
raise AgeValidationError(age, 18)
print("Доступ разрешён.")
try:
check_age(15)
except AgeValidationError as e:
print(e)
От какого класса наследовать
Выбирайте наиболее близкий по смыслу стандартный тип — тогда вашу ошибку сможет поймать и код, который ничего не знает о ваших классах:
| Родитель | Когда выбирать |
|---|---|
ValueError | значение правильного типа, но недопустимое по смыслу |
TypeError | получен неподходящий тип данных |
LookupError | ошибка поиска (общий предок KeyError и IndexError) |
KeyError | отсутствует ключ |
RuntimeError | недопустимое состояние программы |
Exception | подходящего типа нет — общий вариант |
BaseException. От него происходят служебные сигналы KeyboardInterrupt (Ctrl+C) и SystemExit. Пользовательские ошибки наследуют от Exception или его потомков — иначе обычный except Exception перестанет их ловить.
raise и except решают разные задачи
Их часто путают, хотя работают они в противоположных направлениях: raise создаёт и выбрасывает исключение, except — перехватывает его и решает, что делать дальше.
def validate_age(age: int) -> None:
if age < 0:
raise ValueError("Возраст не может быть отрицательным") # сообщаем о проблеме
try:
validate_age(-5)
except ValueError as error: # обрабатываем проблему
print(f"Ошибка ввода: {error}")
Функция, обнаружившая проблему, обычно не знает, как её исправить, — поэтому она сообщает о ней через raise. Решение принимает вызывающий код в except.
__init__ упоминаются в требованиях к уроку, но в исходной лекции не рассматриваются подробно. Официальная документация рекомендует наследовать пользовательские исключения от Exception или его потомков.
Проверки через assert
Инструкция assert проверяет условие и возбуждает AssertionError, если оно ложно. Она используется для внутренних инвариантов и отладки, а не для проверки пользовательского ввода:
# assert_demo.py
def factorial(n: int) -> int:
assert n >= 0, "n must be non-negative"
if n == 0 or n == 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
# factorial(-1) # AssertionError
assert и AssertionError не входят в исходную лекцию, но являются стандартным инструментом Python. При запуске с флагом -O (оптимизация) assert-инструкции игнорируются, поэтому для проверки внешних данных лучше использовать raise.
Логирование ошибок
Модуль logging позволяет записывать сообщения разного уровня важности. В отличие от print, логи можно направить в файл, добавить время, уровень и строку кода.
| Уровень | Метод | Когда использовать |
|---|---|---|
DEBUG | logging.debug | Отладочная информация |
INFO | logging.info | Общая информация о работе |
WARNING | logging.warning | Потенциальные проблемы |
ERROR | logging.error | Ошибки, программа продолжает работу |
CRITICAL | logging.critical | Критические ошибки |
По умолчанию logging выводит только сообщения уровня WARNING и выше. Чтобы видеть DEBUG и INFO, нужно настроить уровень:
# logging_levels.py
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("Отладочное сообщение")
logging.info("Информационное сообщение")
logging.warning("Предупреждение")
logging.error("Ошибка")
logging.critical("Критическая ошибка")
Запись в файл и настройка формата:
# logging_file.py
import logging
logging.basicConfig(
filename="app.log",
format="%(asctime)s - %(filename)s - %(lineno)d - %(levelname)s - %(message)s",
level=logging.ERROR,
)
try:
result = 10 / 0
except ZeroDivisionError as e:
logging.error(f"Ошибка при делении: {e}")
Лучшие практики
- Ловите конкретные исключения. «Голый»
except:илиexcept Exception:перехватывают слишком много, включаяKeyboardInterrupt(если используетсяexcept BaseException) и мешают отладке. - Минимизируйте код в блоке try. Туда помещают только строки, которые действительно могут вызвать ожидаемое исключение.
- Не подавляйте ошибки пустым pass. Если ошибка произошла, либо обработайте её, либо залогируйте.
- Используйте finally для очистки. Закрывайте файлы, соединения и освобождайте ресурсы в любом случае.
- Логируйте ошибки. В production важна история ошибок, а не только сообщение в консоль.
with. Они автоматически освобождают ресурсы даже при исключении и часто заменяют try/finally.
Краткий итог
Исключения — нормальная часть работы программы. Грамотная обработка ошибок делает код устойчивым: программа не падает от любого некорректного ввода, а сообщает о проблеме понятным способом и продолжает работу. Запомните главное: ловите конкретные ошибки, не подавляйте их и логируйте.