Как работать с исключениями в Python
hard

Как работать с исключениями в Python

Не исключено, что вам это пригодится

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

Что такое исключения и какие они бывают

В Python исключение — это событие, которое возникает при выполнении программы и меняет его нормальный поток. Также это объект, который обозначает различные типы состояний отказа программы и содержит информацию об ошибке: 

  • тип ошибки;
  • состояние программы на момент, когда ошибка возникла;
  • описание ошибки.

При этом в Python много встроенных исключений, которые охватывают стандартные типы ошибок:

  • AssertionError — возникает, когда оператор assert терпит неудачу.
  • AttributeError — попытка доступа к атрибуту объекта, которого не существует.
  • EOFError — возникает, когда функция input() достигает конца файла.
  • FileNotFoundError — программа пытается открыть несуществующий файл.
  • FloatingPointError — сбой операции с плавающей запятой.
  • ImportError — неудачная попытка импорта модуля.
  • IndexError — попытка доступа к элементу списка по недопустимому индексу, например когда индекс выходит за пределы допустимого диапазона.
  • IndentationError — неправильные отступы в коде.
  • KeyError — ключ не найден в словаре.
  • KeyboardInterrupt — пользователь нажал клавишу прерывания.
  • MemoryError — операции не хватает памяти.
  • NameError — попытка использовать переменную или функцию, которая не определена.
  • OSError — ошибка, связанная с операционной системой, например при проблеме с файловой системой.
  • RuntimeError — общая ошибка, которая не подпадает под другие категории.
  • SyntaxError — ошибка в синтаксисе кода.
  • TabError — ошибка смешивания табуляции и пробелов для отступов.
  • TypeError — ошибка, которая возникает, когда операция или функция применяются к объекту неподходящего типа.
  • ValueError — возникает, когда функция получает аргумент правильного типа, но некорректного знания.
  • ZeroDivisionError — ошибка при делении на ноль.

Обработка исключений по умолчанию

По умолчанию исключения в Python обрабатываются с помощью блоков try, except, else и finally. Стандартная структура этой конструкции такая:

try:
    # код, который может вызвать исключение
except SomeException:
    # код для обработки исключения, которое может возникнуть в блоке try
else:
    # код, который выполняется, если исключения не было
finally:
    # код, который выполняется в любом случае

Сначала Python выполняет код в блоке try. Если в блоке try возникает исключение, Python останавливает выполнение кода в этом блоке и начинает искать блок except, который соответствует типу возникшего исключения. Если соответствующий блок найден, выполняет код в этом блоке, если нет — исключение передаётся выше по цепочке вызовов (это может привести к завершению программы, если исключение не будет нигде поймано). Если в блоке try не возникло исключение, код выполняется в блоке else (если он есть). Затем, наконец, выполняется код в блоке finally, независимо от того, было исключение или нет.

Блок else бывает полезен, когда нам нужно выполнить какой-то код только в том случае, если блок try завершился успешно. Например, когда нам нужно разделить логику обработки ошибок и основную логику, чтобы код был более читаемым и структурированным. Также это может быть полезно, если мы хотим выполнить какую-то операцию только после успешного выполнения блока try, например когда она требует много ресурсов.

Рассмотрим простой пример с операцией деления на 0:

# выполняем попытку деления
try:
    result = 10 / 0
# исключение при делении на 0
except ZeroDivisionError:
    print("Деление на ноль!")
# этот блок не будет выполнен, поскольку возникло исключение
else:
    print("Ошибок нет, результат:", result)
# этот блок выполнится в любом случае
finally:
    print("Блок finally выполнен.")

Блок finally гарантирует, что критические операции, такие как освобождение ресурсов или закрытие файлов, будут выполнены независимо от того, произошла ошибка или нет.

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

Для обработки различных типов исключений, которые могут возникнуть в блоке try, можно использовать несколько блоков except. Это позволит обрабатывать каждое исключение по-своему, в зависимости от типа исключения.

Например, у нас есть операция деления на какое-то число, которое может ввести пользователь, в том числе из заданного списка. Мы можем предусмотреть исключения для ввода, который не может быть преобразован в целое число, для деления на 0 и для случаев, когда пользователь указывает индекс за пределами списка:

try:
    # код, который может вызвать различные исключения
    x = int(input("Введите число: "))
    result = 10 / x
    my_list = [0, 1, 2, 3]
    print(10 / my_list[x])
# это исключение будет вызвано, если пользователь введёт некорректное значение
except ValueError:
    print("Это не число! Пожалуйста, введите корректное число.")
# это исключение будет вызвано, если пользователь введёт 0
except ZeroDivisionError:
    print("Деление на 0 невозможно! Пожалуйста, введите число, отличное от 0.")
# это исключение будет вызвано, если пользователь введёт число за пределами индексов списка my_list
except IndexError:
    print("Индекс выходит за пределы списка! Пожалуйста, введите число от 0 до 3.")

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

try:
    # код, который может вызвать различные исключения
    x = int(input("Введите число: "))
    result = 10 / x
    my_list = [0, 1, 2, 3]
    print(10 / my_list[x])
# это исключение будет вызвано, если пользователь введёт некорректное значение, 0 или число за пределами индексов списка my list
except (ValueError, ZeroDivisionError, IndexError) as e:
    print(f"Произошла ошибка: {e}")

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

Если мы хотим обработать все возможные исключения, можно использовать блок except, не указывая конкретный тип исключения:

try:
    # код, который может вызвать различные исключения
    x = int(input("Введите число: "))
    result = 10 / x
    my_list = [0, 1, 2, 3]
    print(10 / my_list[x])
except Exception as e:
    print(f"Произошла ошибка: {e}")

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

Использование оператора raise

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

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

def divide(a, b):
    if b == 0:
        # генерируем встроенное исключение
        raise ZeroDivisionError("Деление на ноль невозможно!")
    return a / b

# исключение перехватывается в этом блоке
try:
    result = divide(10, 0)
except ZeroDivisionError as e:
    print(f"Ошибка: {e}")

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

# объявляем класс пользовательского исключения
class NegativeNumberError(Exception):
    """Класс для пользовательского исключения при отрицательном числе."""
    pass

def check_positive(number):
    if number < 0:
        # генерируем пользовательское исключение
        raise NegativeNumberError("Число не должно быть отрицательным!")
    return number

try:
    check_positive(-5)
except NegativeNumberError as e:
    print(f"Ошибка: {e}")

Можно повторно вызвать исключение, которое было перехвачено, чтобы оно могло быть обработано на более высоком уровне:

def process_value(value):
    try:
        if value < 0:
            # генерируем исключение
            raise ValueError("Значение не должно быть отрицательным!")
    except ValueError as e:
        print(f"Перехвачено исключение: {e}")
        # повторно генерируем исключение
        raise

try:
    process_value(-10)
except ValueError as e:
    print(f"Повторно перехвачено исключение: {e}")

Наследование от классов исключений

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

class MyCustomError(Exception):
    """Пользовательское исключение для представления специфического типа ошибки."""

    def __init__(self, message):
        self.message = message
        super().__init__(self.message)

try:
    # генерируем пользовательское исключение
    raise MyCustomError("Это моё пользовательское исключение!")
except MyCustomError as e:
    print(f"Поймано пользовательское исключение: {e}")

Логирование исключений

С помощью модуля logging бывает полезно вести журнал исключений для обнаружения проблем и отладки приложений. При этом исключения могут быть записаны в файл, выведены в консоль, отправлены на электронную почту и так далее.

import logging

# настраиваем конфигурацию логирования
logging.basicConfig(filename='app.log', level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')

try:
    # код, который может вызывать исключения
    result = 10 / 0
except Exception as e:
    # записываем сообщение об ошибке в журнал
    logging.error("Произошло исключение: %s", e)

Обложка:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Маша Климентьева

Соцсети:

Юлия Зубарева

Курс «Мидл Python-разработчик»
Python простой на старте, но чем дальше — тем больше нюансов. Попробуйте курс «Практикума», чтобы выйти на новый уровень. Понятная теория, тренажеры, поддержка ревьюеров и обучение в группе, всё как надо.
Посмотреть
Курс «Мидл Python-разработчик» Курс «Мидл Python-разработчик» Курс «Мидл Python-разработчик» Курс «Мидл Python-разработчик»
Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
hard