Генерация собственных исключений в 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.