Raise: как вручную вызвать исключение

raise: как вручную вызвать исключение Обработка исключений

Привет! Ты уже знаешь, что исключения возникают, когда Python сталкивается с ошибкой: деление на ноль, обращение к несуществующему ключу в словаре, открытие отсутствующего файла. Но что, если ты сам хочешь сказать программе: «Это неправильно, обработай это как ошибку!»? Для этого служит оператор raise.

В этой статье мы разберём:

  • Зачем вообще вручную вызывать исключения
  • Синтаксис raise и как добавить сообщение об ошибке
  • Какой тип исключения выбрать (встроенные и свои)
  • Как создавать собственные классы исключений
  • Проброс исключений с сохранением стека вызовов
  • Связывание исключений с помощью raise ... from

Что нужно знать перед началом

Для этого урока тебе понадобится:

  • Установленный Python
  • Базовое понимание исключений из предыдущих статей
  • Знание конструкции try-except

Совет: Вручную вызывать исключения стоит тогда, когда ты хочешь явно указать на ошибку в своей логике. Это делает код надёжнее и понятнее.

Основная часть

1. Зачем вручную вызывать исключения?

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

«Обычно код, который вызывает функцию, а не сама функция, знает, как обработать исключение. Поэтому вы часто увидите оператор raise внутри функции, а операторы try и except — в коде, вызывающем функцию.» 

Совет: Программист, который будет использовать твой код, будет благодарен за чёткие и понятные сообщения об ошибках. raise — это твой способ сказать «Эй, здесь что-то пошло не так!».

Синтаксис raise

Оператор raise состоит из ключевого слова raise и объекта исключения (или класса исключения) .

Простейший способ — вызвать встроенное исключение

# Просто поднимаем исключение без дополнительной информации
raise ValueError

Результат:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError

С сообщением об ошибке (рекомендуется)

raise ValueError('А вот это уже полезное сообщение!')

Результат:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: А вот это уже полезное сообщение!

Совет: Всегда добавляй сообщение к исключению. Это помогает понять, что именно пошло не так.

Синтаксис: raise + Exception('ваше сообщение') 

Можно передать несколько аргументов

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

raise ValueError('Ошибка', 'дополнительный_параметр', 123)

В обработчике: эти данные будут доступны через err.args.

Выбор правильного типа исключения

Python предлагает множество встроенных исключений. Важно выбирать самое подходящее .

ИсключениеКогда использовать
ValueErrorПолучен аргумент правильного типа, но с недопустимым значением. Например, возраст не может быть отрицательным .
TypeErrorПолучен аргумент неправильного типа. Например, ждали число, а пришла строка .
KeyErrorВ словаре нет такого ключа.
IndexErrorИндекс выходит за пределы списка.
FileNotFoundErrorФайл не найден.
RuntimeErrorДля всего, что не подходит под другие категории.

Совет: Никогда не вызывай raise Exception('...') без особой причины. Это слишком общий тип, и его перехват может скрыть другие, более серьёзные ошибки .

Пример правильного выбора:

Допустим, мы пишем функцию для установки возраста.

def set_age(age):
    if not isinstance(age, int):
        # Правильный тип? Нет! TypeError
        raise TypeError(f'Возраст должен быть числом, а не {type(age).__name__}')
    if age < 0:
        # Тип правильный (int), но значение недопустимое. ValueError!
        raise ValueError('Возраст не может быть отрицательным')
    print(f'Возраст установлен: {age}')

Пример использования:

try:
    set_age(-5)
except TypeError as e:
    print(f'Ошибка типа: {e}')
except ValueError as e:
    print(f'Ошибка значения: {e}')

Результат:

Ошибка значения: Возраст не может быть отрицательным

Создание своих исключений

Бывает, что встроенные исключения слишком общие для твоей задачи. Тогда можно создать свой класс исключения, унаследовав его от Exception.

class ValidationError(Exception):
    """Исключение для ошибок валидации данных."""
    def __init__(self, message, field=None):
        super().__init__(message)
        self.field = field

def validate_user(name, age):
    if not name:
        raise ValidationError('Имя пользователя не может быть пустым', field='name')
    if age < 0:
        raise ValidationError('Возраст не может быть отрицательным', field='age')
    return True

# Использование
try:
    validate_user('', -5)
except ValidationError as e:
    print(f'Ошибка в поле "{e.field}": {e}')

Совет: Называй свои исключения с суффиксом Error или Exception, чтобы было понятно, что это класс ошибки.

Проброс исключений (re-raise)

Иногда ты хочешь перехватить исключение, записать его в лог или сделать что-то ещё, а потом позволить ему всплыть выше, чтобы его обработал другой код. Для этого используется raise без аргументов — он пробрасывает текущее исключение дальше.

import logging

logger = logging.getLogger(__name__)

def process_data(data):
    try:
        # ... какой-то опасный код ...
        return data[0] / data[1]
    except ZeroDivisionError as e:
        logger.error('Попытка деления на ноль!')
        raise  # Пробрасываем исключение дальше

# Вызывающий код
try:
    result = process_data([1, 0])
except ZeroDivisionError:
    print('Кто-то пытался поделить на ноль!')

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

Связывание исключений: raise … from

В Python 3 появилась возможность связывать исключения. Это полезно, когда ты хочешь заменить одно исключение другим, но при этом сохранить информацию о первопричине.

try:
    # Какой-то код, который может вызвать ошибку
    int('abc')
except ValueError as e:
    # Мы хотим сказать: "Это произошло из-за ошибки в данных"
    raise RuntimeError('Не удалось преобразовать данные') from e

Результат:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ValueError: invalid literal for int() with base 10: 'abc'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Не удалось преобразовать данные

Совет: Это помогает разработчику понять цепочку ошибок, особенно когда ты работаешь с разными уровнями абстракции в коде.

Задачи для закрепления

Задача 1. Напиши функцию divide(a, b), которая вызывает ZeroDivisionError, если b == 0.

Задача 2. Напиши функцию set_age(age), которая вызывает ValueError при отрицательном возрасте.

Задача 3. Создай свой класс исключения NegativeNumberError и используй его для валидации положительных чисел.

Задача 4. Перехвати TypeError, а затем пробрось его дальше с помощью raise.

Задача 5. Изучи разницу между raise и raise ... from на примере.

Ответы:

Задача 1.

def divide(a, b):
    if b == 0:
        raise ZeroDivisionError('На ноль делить нельзя!')
    return a / b

Задача 2.

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

Задача 3.

class NegativeNumberError(Exception):
    pass

def check_positive(num):
    if num < 0:
        raise NegativeNumberError(f'Число {num} отрицательное!')
    return True

Задача 4.

def func():
    try:
        'abc' + 5
    except TypeError:
        print('Логируем ошибку...')
        raise  # Пробрасываем TypeError дальше

Задача 5.

raise — просто выбрасывает новое исключение. raise ... from — связывает новое исключение с предыдущим, показывая, что они связаны по смыслу.

Нюансы и подводные камни

raise перезаписывает контекст

Если ты просто используешь raise с новым исключением, ты потеряешь информацию о том, что произошло ранее. from помогает этого избежать .

Не вызывай Exception в лоб

Использование raise Exception('...') затрудняет отладку, так как это слишком общее исключение. Всегда старайся использовать что-то более конкретное .

Игнорирование ошибок

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

Частые ошибки и как их избежать

Ошибка 1: Вызов слишком общего исключения

# Плохо — лень, но скрывает детали
raise Exception('Ошибка!')

# Хорошо — конкретное исключение
raise ValueError('Передано неверное значение')

Ошибка 2: Попытка повторного вызова без сохранения контекста

# Плохо — потеряли стек вызовов
try:
    1 / 0
except ZeroDivisionError:
    raise ValueError('Деление на ноль')
# Теряем оригинальную причину!

# Хорошо — связываем исключения
try:
    1 / 0
except ZeroDivisionError as e:
    raise ValueError('Деление на ноль') from e

Ошибка 3: Использование устаревшего синтаксиса

Не используй устаревший синтаксис Python 2: raise ValueError, 'message' .

Шпаргалка

Что нужноКак пишется
Поднять исключениеraise ValueError
Поднять с сообщениемraise ValueError('Текст ошибки')
Пробросить текущее исключениеraise
Связать исключениеraise NewError('...') from old_error
Создать свой класс ошибкиclass MyError(Exception): pass

Заключение

Сегодня мы научились:

  • Вручную вызывать исключения с помощью raise
  • Подбирать подходящий встроенный тип исключения
  • Создавать свои классы исключений
  • Пробрасывать исключения дальше с сохранением стека
  • Связывать исключения с помощью raise ... from

КВИЗ

Что дальше?

Оцените статью
IMI-DS - PYTHON LERNEN
Содержание
Оглавление ×