Генерация собственных исключений в Python: практическое руководство с примерами

Раздел: Python -> Исключения и обработка ошибок

Основы создания исключений в Python

Основной способ создать исключение в Python - использовать оператор raise с объектом исключения. Можно использовать встроенные классы (ValueError, TypeError и т.д.) или создать собственный класс, унаследовав его от Exception.

def check_age(age):
    if age < 0:
        raise ValueError('Возраст не может быть отрицательным')
    return age

try:
    check_age(-5)
except ValueError as e:
    print(e)

Python создать ошибку (создание исключений в python)

Возраст не может быть отрицательным

класс ошибок python (классы исключений в python)

Цель: остановить выполнение при недопустимых данных, передав информацию об ошибке. Используется для валидации входных параметров, контроля состояния.

Как проверить условие и автоматически выбросить AssertionError?

Иногда требуется просто убедиться, что условие истинно, и если нет - выдать ошибку с сообщением. Для этого существует assert.

x = -1
assert x >= 0, 'x должен быть неотрицательным'

Python except (обработка исключений в python с помощью try-except)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: x должен быть неотрицательным

Цель: быстрая проверка предусловий и постусловий, особенно в отладке. Случаи использования: проверка аргументов функций, инвариантов в тестах.

Проблемы и типичные ошибки:

  • assert можно отключить глобально флагом -O, поэтому не стоит полагаться на него для критичной валидации в продакшене.
  • Сравнение с raise: assert выбрасывает только AssertionError без возможности выбора другого типа исключения.

Как создать собственный тип исключения с дополнительными данными?

Для более точной классификации ошибок определяют класс, наследующий от Exception. Можно добавить атрибуты для передачи контекста.

class NegativeNumberError(Exception):
    def __init__(self, value, message='Число не должно быть отрицательным'):
        self.value = value
        self.message = f'{message}: {value}'
        super().__init__(self.message)

def square_root(x):
    if x < 0:
        raise NegativeNumberError(x)
    return x ** 0.5

try:
    square_root(-9)
except NegativeNumberError as e:
    print(f'Ошибка: {e}, значение: {e.value}')
Ошибка: Число не должно быть отрицательным: -9, значение: -9

Цель: создание семантически богатых исключений, которые легко обрабатывать по типу. Используется в библиотеках и крупных проектах для точной обработки ошибок.

Проблемы и типичные ошибки:

  • Забыть вызвать super().__init__() - тогда сообщение не передается в базовый класс, и str(e) может быть пустым.
  • Неправильное наследование: наследовать от BaseException вместо Exception может привести к непреднамеренному перехвату системных исключений.

Как сохранить цепочку исключений при проброске новой ошибки?

При обработке одного исключения может потребоваться выбросить другое, но сохранить контекст исходного. Для этого используется raise ... from ....

def parse_int(s):
    try:
        return int(s)
    except ValueError as e:
        raise TypeError('Ожидалась строка с целым числом') from e

try:
    parse_int('abc')
except TypeError as e:
    print(e)
    print('Причина:', e.__cause__)
Ожидалась строка с целым числом
Причина: invalid literal for int() with base 10: 'abc'

Цель: не потерять первоначальную причину ошибки, упростить отладку. Используется в функциях-обёртках, API, где нужно преобразовать низкоуровневые исключения в высокоуровневые.

Проблемы и типичные ошибки:

  • Забыть указать from - тогда исходное исключение будет потеряно (подавлено).
  • Использовать raise ... from None для сознательного подавления исходной ошибки, если это необходимо.

Как пробросить исключение дальше после частичной обработки?

Иногда нужно выполнить некоторое действие (логирование, очистку) при возникновении ошибки, но затем дать ей всплыть наверх. Используется raise без аргументов внутри блока except.

def process(data):
    try:
        result = 1 / data
    except ZeroDivisionError:
        print('Предупреждение: попытка деления на ноль')
        raise  # повторный вызов того же исключения

try:
    process(0)
except ZeroDivisionError:
    print('Обработка на верхнем уровне')
Предупреждение: попытка деления на ноль
Обработка на верхнем уровне

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

Проблемы и типичные ошибки:

  • После raise можно случайно изменить объект исключения, если использовать raise e (с объектом) - это сбросит трассировку. Использовать raise без аргумента сохраняет оригинальную трассировку.
  • Если нужно изменить тип исключения, лучше использовать raise ... from.

Расширенные примеры создания исключений

Пример 1: Иерархия пользовательских исключений для приложения

Пример
class AppError(Exception):
    """Базовое исключение приложения"""
    pass

class InputError(AppError):
    def __init__(self, field, message):
        self.field = field
        self.message = message
        super().__init__(f'{field}: {message}')

class ProcessingError(AppError):
    def __init__(self, data, message):
        self.data = data
        self.message = message
        super().__init__(f'Data {data}: {message}')

def process_input(user_input):
    if not user_input:
        raise InputError('username', 'Поле обязательно')
    if len(user_input) < 3:
        raise ProcessingError(user_input, 'Слишком короткое')

try:
    process_input('ab')
except InputError as e:
    print('InputError:', e)
except ProcessingError as e:
    print('ProcessingError:', e)
except AppError as e:
    print('Other AppError:', e)
ProcessingError: Data ab: Слишком короткое

Создана иерархия: базовый класс AppError и два наследника. Это позволяет перехватывать ошибки на разных уровнях детализации.

Пример 2: Исключение с методами для извлечения информации

Пример
class HTTPError(Exception):
    def __init__(self, status_code, message):
        self.status_code = status_code
        self.message = message
        super().__init__(message)
    def is_client_error(self):
        return 400 <= self.status_code < 500

try:
    raise HTTPError(404, 'Not Found')
except HTTPError as e:
    print(e.is_client_error())  # True
True

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

Пример 3: Цепочка исключений и подавление через from None

Пример
def connect():
    raise ConnectionError('Сервер недоступен')

def get_data():
    try:
        connect()
    except ConnectionError as e:
        raise RuntimeError('Не удалось получить данные') from e

def main():
    try:
        get_data()
    except RuntimeError as e:
        print('Основная ошибка:', e)
        print('Причина:', e.__cause__)

main()
Основная ошибка: Не удалось получить данные
Причина: Сервер недоступен

Использование from e сохраняет цепочку. Для подавления исходной ошибки применяют raise ... from None:

Пример
def read_config(path):
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError:
        raise RuntimeError('Файл конфигурации отсутствует') from None

try:
    read_config('missing.txt')
except RuntimeError as e:
    print(e)
    print('__cause__:', e.__cause__)
Файл конфигурации отсутствует
__cause__: None

Пример 4: Сравнение assert и raise для валидации

Пример
def set_age_assert(age):
    assert isinstance(age, int), 'Возраст должен быть целым числом'
    assert 0 <= age <= 150, 'Возраст вне допустимого диапазона'
    return age

def set_age_raise(age):
    if not isinstance(age, int):
        raise TypeError('Возраст должен быть целым числом')
    if not (0 <= age <= 150):
        raise ValueError('Возраст вне допустимого диапазона')
    return age

# Пример вызова с assert (может быть отключено)
try:
    set_age_assert('20')
except AssertionError as e:
    print('AssertionError:', e)

# Пример вызова с raise
try:
    set_age_raise('20')
except TypeError as e:
    print('TypeError:', e)
AssertionError: Возраст должен быть целым числом
TypeError: Возраст должен быть целым числом

assert компактен, но менее гибок. raise даёт точный контроль над типом исключения и надёжен в production.

Создание исключений в Python - comments

En
Python создать ошибку (python)