Что такое исключения?

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

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

Типы исключений в Python

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

Распространенные типы исключений
# Примеры различных типов исключений

# ValueError - неподходящее значение
try:
    число = int("не число")
except ValueError as e:
    print(f"Ошибка значения: {e}")

# IndexError - выход за пределы индекса
try:
    список = [1, 2, 3]
    элемент = список[10]
except IndexError as e:
    print(f"Ошибка индекса: {e}")

# KeyError - отсутствующий ключ в словаре
try:
    словарь = {"ключ": "значение"}
    значение = словарь["несуществующий_ключ"]
except KeyError as e:
    print(f"Ошибка ключа: {e}")

# ZeroDivisionError - деление на ноль
try:
    результат = 10 / 0
except ZeroDivisionError as e:
    print(f"Ошибка деления: {e}")

# TypeError - неподходящий тип данных
try:
    результат = "строка" + 5
except TypeError as e:
    print(f"Ошибка типа: {e}")

Блок try-except

Основной способ обработки исключений в Python - это использование блоков try и except:

Базовая обработка исключений
# Простая обработка исключений
try:
    # Код, который может вызвать исключение
    число = int(input("Введите число: "))
    результат = 10 / число
    print(f"Результат: {результат}")
except ValueError:
    # Обработка конкретного типа исключения
    print("Ошибка: введено не число!")
except ZeroDivisionError:
    # Обработка другого типа исключения
    print("Ошибка: деление на ноль!")
except Exception as e:
    # Обработка всех остальных исключений
    print(f"Произошла неизвестная ошибка: {e}")
else:
    # Выполняется, если исключений не было
    print("Операция выполнена успешно!")
finally:
    # Выполняется всегда, независимо от исключений
    print("Блок finally выполнен")

# Пример с обработкой нескольких исключений
def разделить_числа(делимое, делитель):
    try:
        результат = делимое / делитель
        return результат
    except ZeroDivisionError:
        print("Ошибка: деление на ноль!")
        return None
    except TypeError:
        print("Ошибка: неподходящий тип данных!")
        return None

# Тестирование функции
print(разделить_числа(10, 2))    # 5.0
print(разделить_числа(10, 0))    # Ошибка: деление на ноль!
print(разделить_числа("10", 2))  # Ошибка: неподходящий тип данных!

Блок else и finally

Дополнительные блоки else и finally позволяют более точно управлять обработкой исключений:

Использование else и finally
# Пример с else и finally
def обработать_файл(имя_файла):
    try:
        # Попытка открыть файл
        файл = open(имя_файла, 'r', encoding='utf-8')
    except FileNotFoundError:
        # Обработка исключения, если файл не найден
        print(f"Ошибка: файл '{имя_файла}' не найден")
        return None
    except PermissionError:
        # Обработка исключения, если нет прав доступа
        print(f"Ошибка: нет прав доступа к файлу '{имя_файла}'")
        return None
    else:
        # Выполняется только если исключений не было
        print(f"Файл '{имя_файла}' успешно открыт")
        содержимое = файл.read()
        файл.close()
        return содержимое
    finally:
        # Выполняется всегда, независимо от исключений
        print("Операция завершена")

# Тестирование функции
print("Попытка открыть существующий файл:")
результат = обработать_файл("test.txt")
print(f"Результат: {результат}")

print("\nПопытка открыть несуществующий файл:")
результат = обработать_файл("несуществующий.txt")
print(f"Результат: {результат}")

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

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

Пользовательские исключения
# Создание собственного исключения
class НедостаточноСредствError(Exception):
    def __init__(self, баланс, сумма):
        self.баланс = баланс
        self.сумма = сумма
        super().__init__(f"Недостаточно средств: баланс {баланс}, требуется {сумма}")

class НедопустимыйВозрастError(ValueError):
    def __init__(self, возраст):
        self.возраст = возраст
        super().__init__(f"Недопустимый возраст: {возраст}. Возраст должен быть положительным числом.")

# Использование собственных исключений
class БанковскийСчет:
    def __init__(self, владелец, баланс=0):
        self.владелец = владелец
        self.баланс = баланс
    
    def снять_деньги(self, сумма):
        if сумма > self.баланс:
            raise НедостаточноСредствError(self.баланс, сумма)
        self.баланс -= сумма
        return self.баланс
    
    def пополнить_счет(self, сумма):
        if сумма <= 0:
            raise ValueError("Сумма пополнения должна быть положительной")
        self.баланс += сумма
        return self.баланс

def проверить_возраст(возраст):
    if возраст < 0:
        raise НедопустимыйВозрастError(возраст)
    return True

# Тестирование собственных исключений
счет = БанковскийСчет("Иван", 100)

try:
    счет.снять_деньги(150)
except НедостаточноСредствError as e:
    print(f"Ошибка: {e}")
    print(f"Текущий баланс: {e.баланс}")
    print(f"Запрошенная сумма: {e.сумма}")

try:
    проверить_возраст(-5)
except НедопустимыйВозрастError as e:
    print(f"Ошибка возраста: {e}")

Цепочки исключений

Python позволяет создавать цепочки исключений с помощью raise ... from ...:

Цепочки исключений
# Пример цепочки исключений
def преобразовать_данные(данные):
    try:
        # Попытка преобразования данных
        числа = [int(x) for x in данные.split(',')]
        return числа
    except ValueError as e:
        # Перевыброс исключения с дополнительной информацией
        raise TypeError(f"Невозможно преобразовать данные: {данные}") from e

def обработать_ввод(ввод_пользователя):
    try:
        return преобразовать_данные(ввод_пользователя)
    except TypeError as e:
        # Дополнительная обработка с сохранением цепочки исключений
        print(f"Ошибка обработки ввода: {e}")
        if e.__cause__:
            print(f"Причина: {e.__cause__}")
        raise  # Перевыброс текущего исключения

# Тестирование цепочек исключений
try:
    результат = обработать_ввод("1,2,три,4")
except TypeError as e:
    print(f"Финальная ошибка: {e}")
    if e.__cause__:
        print(f"Первоначальная ошибка: {e.__cause__}")

Практические примеры

Рассмотрим практические примеры использования обработки исключений:

Калькулятор с обработкой ошибок
class Калькулятор:
    def сложить(self, a, b):
        try:
            return a + b
        except TypeError:
            raise TypeError("Невозможно сложить данные типы")
    
    def разделить(self, a, b):
        try:
            if b == 0:
                raise ZeroDivisionError("Деление на ноль")
            return a / b
        except TypeError:
            raise TypeError("Невозможно разделить данные типы")
    
    def вычислить(self, выражение):
        try:
            # Простой парсер выражений
            части = выражение.split()
            if len(части) != 3:
                raise ValueError("Неверный формат выражения")
            
            a = float(части[0])
            оператор = части[1]
            b = float(части[2])
            
            if оператор == "+":
                return self.сложить(a, b)
            elif оператор == "/":
                return self.разделить(a, b)
            else:
                raise ValueError(f"Неизвестный оператор: {оператор}")
        except ValueError as e:
            raise ValueError(f"Ошибка в выражении '{выражение}': {e}") from e

# Использование калькулятора с обработкой ошибок
калькулятор = Калькулятор()

выражения = [
    "10 + 5",
    "20 / 4",
    "15 / 0",  # Ошибка: деление на ноль
    "abc + 5",  # Ошибка: неверный формат числа
    "10 * 3",   # Ошибка: неизвестный оператор
    "5 +"       # Ошибка: неверный формат выражения
]

for выражение in выражения:
    try:
        результат = калькулятор.вычислить(выражение)
        print(f"{выражение} = {результат}")
    except (ValueError, ZeroDivisionError, TypeError) as e:
        print(f"Ошибка в '{выражение}': {e}")

Упражнения для самостоятельного решения

Попробуйте решить следующие задачи, применяя полученные знания об обработке исключений:

Упражнения

Создайте функцию безопасное_деление(делимое, делитель), которая безопасно выполняет деление двух чисел. Функция должна обрабатывать следующие исключения:

  • TypeError - если один из аргументов не является числом
  • ZeroDivisionError - если делитель равен нулю

Функция должна возвращать результат деления или None, если произошла ошибка.

def безопасное_деление(делимое, делитель):
    try:
        return делимое / делитель
    except TypeError:
        print("Ошибка: оба аргумента должны быть числами")
        return None
    except ZeroDivisionError:
        print("Ошибка: деление на ноль")
        return None

# Тестирование
print(безопасное_деление(10, 2))    # 5.0
print(безопасное_деление(10, 0))    # Ошибка: деление на ноль
print(безопасное_деление("10", 2))  # Ошибка: оба аргумента должны быть числами

Создайте функцию проверить_возраст(возраст), которая проверяет, что возраст является положительным целым числом. Создайте собственное исключение НедопустимыйВозрастError. Функция должна:

  • Генерировать TypeError, если возраст не является числом
  • Генерировать НедопустимыйВозрастError, если возраст отрицательный
  • Возвращать True, если возраст допустим
class НедопустимыйВозрастError(Exception):
    def __init__(self, возраст):
        super().__init__(f"Недопустимый возраст: {возраст}. Возраст должен быть положительным числом.")

def проверить_возраст(возраст):
    if not isinstance(возраст, (int, float)):
        raise TypeError("Возраст должен быть числом")
    
    if возраст < 0:
        raise НедопустимыйВозрастError(возраст)
    
    return True

# Тестирование
try:
    print(проверить_возраст(25))   # True
    print(проверить_возраст(-5))   # НедопустимыйВозрастError
except (TypeError, НедопустимыйВозрастError) as e:
    print(f"Ошибка: {e}")

Создайте функцию прочитать_файл(имя_файла), которая безопасно читает содержимое файла. Функция должна обрабатывать следующие исключения:

  • FileNotFoundError - если файл не существует
  • PermissionError - если нет прав доступа к файлу
  • UnicodeDecodeError - если файл имеет неподдерживаемую кодировку

Функция должна возвращать содержимое файла или None, если произошла ошибка.

def прочитать_файл(имя_файла):
    try:
        with open(имя_файла, 'r', encoding='utf-8') as файл:
            return файл.read()
    except FileNotFoundError:
        print(f"Ошибка: файл '{имя_файла}' не найден")
        return None
    except PermissionError:
        print(f"Ошибка: нет прав доступа к файлу '{имя_файла}'")
        return None
    except UnicodeDecodeError:
        print(f"Ошибка: файл '{имя_файла}' имеет неподдерживаемую кодировку")
        return None

# Тестирование
print(прочитать_файл("существующий.txt"))      # Содержимое файла
print(прочитать_файл("несуществующий.txt"))   # Ошибка: файл не найден
Предыдущий урок Следующий урок