Гид по технологиям

Пользовательские исключения в Python — руководство

6 min read Python Обновлено 09 Jan 2026
Пользовательские исключения в Python — руководство
Пользовательские исключения в Python — руководство

О чём эта статья

Двоичные значения, проецируемые на спину человека

Эта подробная инструкция объясняет, зачем нужны пользовательские (кастомные) исключения в Python, как их правильно определять, возбуждать, обрабатывать и группировать. Вы найдёте практические рекомендации, шаблоны, чек-листы для ролей и примеры распространённых ошибок при использовании пользовательских исключений.

Важно: статья ориентирована на разработчиков и командных инженеров. Примеры даны на Python 3.x.

Когда встроенных исключений недостаточно

Иллюстрация: владение ошибкой, символика «own your error»

Стандартные исключения Python (ValueError, TypeError, FileNotFoundError и т. п.) покрывают множество сценариев, но не всегда отражают прикладную суть ошибки в вашем приложении. Когда в логах и обработчиках требуется семантика уровня предметной области (domain), полезно ввести собственные типы ошибок:

  • чтобы отделить ошибки валидации пользователя от внутренних ошибок сервиса;
  • чтобы группировать все ошибки, связанные с внешним API, в одну иерархию;
  • чтобы сделать код обработчиков понятным без изучения реализации.

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

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

class MyCustomError(Exception):
    def __init__(self, message=None):
        self.message = message
        super().__init__(message)

Этот класс принимает необязательное сообщение. Вызов super().init(message) сохраняет стандартное поведение Exception (строка и стек).

Как возбуждать (raise) пользовательские исключения

Чтобы вызвать исключение, используйте raise с экземпляром класса или с самим классом (короткая форма для исключений без аргументов):

if True:
    raise MyCustomError("A Custom Error Was Raised!!!.")

# Или без аргументов
if True:
    raise MyCustomError

Обычно рекомендуется всегда передавать сообщение и, при необходимости, структурированные поля (код ошибки, контекст) — это облегчает логирование и диагностику.

Иллюстрация: возбужденное пользовательское исключение

Обработка пользовательских исключений

Обработка ошибок ничем не отличается от работы с встроенными исключениями: try/except/finally остаётся стандартной конструкцией.

try:
    print("Hello, You're learning how to MakeUseOf Custom Errors")
    raise MyCustomError("Opps, Something Went Wrong!!!.")
except MyCustomError as err:
    print(f"Error: {err}")
finally:
    print("Done Handling Custom Error")

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

try:
    raise KeyboardInterrupt
except MyCustomError as err:
    print(f"Error: {err}")
finally:
    print("Did not Handle the KeyboardInterrupt Error. Can Only Handle MyCustomError")

В этом примере KeyboardInterrupt не перехвачен, выполняется finally, затем исключение поднимается дальше.

Стектрейс с необработанным исключением

Наследование и групповая обработка ошибок

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

Для комплексных приложений имеет смысл построить иерархию исключений. Создайте базовый класс для семантически связанных ошибок, затем — более узкие подклассы.

Пример: ошибки, связанные с вызовами внешнего API.

class BaseAPIException(Exception):
    """Base class for API-related exceptions."""
    def __init__(self, message):
        super().__init__(message)
        self.message = message

class APINotFoundError(BaseAPIException):
    """Raised when the requested resource is not found in the API."""
    pass

class APIAuthenticationError(BaseAPIException):
    """Raised when there's an issue with authentication to the API."""
    pass

class APIRateLimitExceeded(BaseAPIException):
    """Raised when the rate limit for API requests is exceeded."""
    pass

Их удобно перехватывать как по конкретному типу, так и по базовому классу, чтобы обрабатывать общие случаи:

try:
    # Симуляция ошибки API
    raise APINotFoundError("Requested resource not found.")
except APINotFoundError as err:
    print(f"API Not Found Error: {err}")
except APIAuthenticationError as err:
    print(f"API Authentication Error: {err}")
except APIRateLimitExceeded as err:
    print(f"API Rate Limit Exceeded: {err}")
except BaseAPIException as err:
    print(f"Unknown API Exception: {err}")

Последний except работает как catch-all для ошибок, связанных с API.

Оборачивание (wrapping) исключений для контекста

Иллюстрация: оборачивание исключений

Оборачивание — это приём, когда вы ловите низкоуровневое исключение, добавляете контекст и поднимаете своё исключение, сохранив ссылку на исходное через from.

def request_api():
    try:
        # Предположим, внешний код выбросил LookupError
        raise LookupError("Sorry, You Encountered A LookUpError !!!")
    except LookupError as original_exception:
        try:
            # Оборачиваем оригинальную ошибку собственным типом
            raise APINotFoundError("Requested resource not found.") from original_exception
        except APINotFoundError as wrapped_exception:
            print(f"Caught wrapped API exception: {wrapped_exception}")
            raise

try:
    request_api()
except APINotFoundError as err:
    print(f"Caught API exception: {err.__cause__}")

Атрибут cause позволяет отследить первопричину. Это особенно полезно при логировании и в отладочных страницах ошибок.

Когда пользовательские исключения НЕ работают

  • Если ваша библиотека предназначена для низкоуровневого кода (язык/парсер), стандартные Exception-подклассы могут быть предпочтительнее.
  • Если API широко используется сторонними библиотеками, добавление новых публичных типов исключений требует поддерживаемой семантики назад-совместимости.
  • Не стоит создавать слишком мелкие типы исключений, которые затем никем не перехватываются — это усложнит код.

Альтернативные подходы

  • Использовать коды ошибок/enum в возвращаемых значениях вместо исключений (подходит для high-throughput кода, где исключения дороги по производительности).
  • Возвращать Result-объект (patent from functional languages) — например, NamedTuple с полями success/data/error.
  • Для внешних API использовать библиотечные адаптеры, которые переводят сторонние исключения в вашу иерархию.

Практические правила и соглашения (Best practices)

  • Именуйте классы исключений с суффиксом Error: MyDomainError, APIAuthenticationError.
  • Всё прикладное пространство ошибок группируйте под одним корневым классом (например, MyAppError).
  • Храните минимум состояний в исключении: message, code (если нужно), context (словарь с небольшой диагностикой).
  • Логируйте исключения с полным стеком в месте, где вы принимаете решения о retry/rollback.
  • Не используйте исключения для обычного потока управления (avoid using exceptions for flow control).

Чек-листы по ролям

Для разработчика, пишущего фичу

  • Есть ли смысл в отдельном типе исключения для этой ошибки?
  • Исключение-наследник оформлен от базового класса приложения.
  • Передаётся диагностическая информация (message, context).
  • Написаны unit-тесты, проверяющие, что исключение выбрасывается в нужных местах.

Для инженера поддержки / техлида

  • [ ] Логи содержат cause и контекст.
  • Исключения, которые нужны для мониторинга, промаркированы (code/labels).
  • Есть обработчики, выполняющие retry/rollback там, где это безопасно.

Критерии приёмки (пример)

  • Новое исключение документировано в README и CHANGELOG.
  • Юнит-тесты покрывают положительные и негативные сценарии.
  • При интеграционных тестах исключение корректно сериализуется в лог.
  • Обработчики приложения (middleware) не ломаются при добавлении нового типа исключения.

Unit-тесты и кейсы приёмки

Примеры тестов, которые стоит написать:

  • Проверить, что при определённых входных данных возбуждается конкретный тип исключения.
  • Проверить, что исключение содержит поле message и ожидаемый текст.
  • Проверить, что при оборачивании cause ссылается на исходную ошибку.
  • Проверить, что middleware приложения возвращает правильный HTTP-код для данного исключения.

Шаблон: SOP для добавления нового исключения

  1. Обсудить необходимость с командой (соглашения по именованию и иерархии).
  2. Создать класс в модуле errors/ или exceptions/.
  3. Добавить тесты (unit + интеграционные при необходимости).
  4. Обновить документацию и CHANGELOG.
  5. При релизе отметить обратную совместимость.

Краткие сниппеты-справочники (cheat sheet)

Определение простого исключения:

class MyDomainError(Exception):
    """Ошибки предметной области приложения."""
    def __init__(self, message: str, code: str = None, context: dict = None):
        self.message = message
        self.code = code
        self.context = context or {}
        super().__init__(message)

Оборачивание:

try:
    external_call()
except SomeLibError as e:
    raise MyDomainError("Не удалось выполнить external_call", code="EXT_CALL_FAIL") from e

Логирование с трейсом:

import logging
logger = logging.getLogger(__name__)

try:
    do_work()
except MyDomainError:
    logger.exception("Ошибка предметной области: %s", exc_info=True)
    raise

Ментальные модели и эвристики

  • Ошибка — это не только сообщение: это контракт между уровнем, где ошибка возникла, и уровнем, который её будет обрабатывать.
  • Используйте иерархии исключений как «фильтры»: от общего к частному.
  • Оборачивайте системные ошибки (IOError, LookupError) в доменные ошибки на границе инфраструктуры.

Примеры ошибок и когда их выбирать

  • ValueError / TypeError — при нарушении ожиданий по типам и значениям;
  • MyDomainError — когда ошибка входит в предметную область (недостаточно общая для ValueError);
  • BaseAPIException и его наследники — при работе с внешними API.

Маленький глоссарий (1 строка каждый)

  • Исключение: объект, сигнализирующий о ненормальной ситуации в программе.
  • Оборачивание (wrapping): перевод низкоуровневого исключения в доменное с сохранением причины.
  • cause: атрибут исключения, указывающий на оригинальную ошибку при использовании from.

Decision flow (простое дерево решения)

flowchart TD
  A[Произошла ошибка] --> B{Это ошибка уровня библиотеки?}
  B -- Да --> C[Не оборачивать, использовать стандартное исключение]
  B -- Нет --> D{Связанa ли ошибка с предметной областью?}
  D -- Да --> E[Создать/использовать доменное исключение]
  D -- Нет --> F[Использовать общие Exception или возврат Result]
  E --> G{Нужна ли иерархия?}
  G -- Да --> H[Создать базовый класс и подклассы]
  G -- Нет --> I[Создать одиночный тип исключения]

Совместимость, миграции и заметки по API

  • Если ваше исключение становится частью публичного API, его изменение — breaking change. Планируйте версии и депрекейт-коммуникацию.
  • Для миграции из строковых кодов ошибок на типы — добавьте пакет-адаптер, который переводит старые коды в новые исключения.

Частые ошибки и анти-паттерны

  • Создание сотен микроскопических типов исключений, которые никто не перехватывает.
  • Хранение большого объёма данных (большие объекты) в атрибутах исключения — это может привести к утечкам памяти.
  • Проглатывание исключений без логирования (silent fail).

Заключение

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

Ключевые рекомендации:

  • Держите исключения простыми и предсказуемыми.
  • Используйте наследование для группирования.
  • Логируйте контекст и первопричину.

Иллюстрация: завершение обработки исключения

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Градиенты в Canva: добавить и настроить
Дизайн

Градиенты в Canva: добавить и настроить

Ошибка Disabled accounts can't be contacted в Instagram
Социальные сети

Ошибка Disabled accounts can't be contacted в Instagram

Генерация случайных чисел в Google Sheets
Google Таблицы

Генерация случайных чисел в Google Sheets

Прокручиваемые скриншоты в Windows 11
Windows

Прокручиваемые скриншоты в Windows 11

Как установить корпусной вентилятор в ПК
Железо

Как установить корпусной вентилятор в ПК

Check In в iOS 17: настройка и безопасность
How-to

Check In в iOS 17: настройка и безопасность