Обработка исключений в Python: try, except, finally — на понятных примерах

Когда программа делает что-то с внешними данными (читает файл, делит числа, получает ввод от пользователя), всегда есть риск, что что-то пойдёт не так: файл не найдётся, на ноль делить нельзя, а пользователь вместо числа введёт буквы. В Python для работы с такими ситуациями используют конструкцию try/except/finally. Её задача — не дать программе упасть с непонятной ошибкой, а обработать проблему аккуратно и, если нужно, продолжить работу.


Как устроена конструкция

Базовая схема выглядит так:

python
try:
    # код, который может вызвать ошибку
except SomeException:
    # что делать, если ошибка этого типа случилась
else:
    # выполняется, если ошибок не было (опционально)
finally:
    # выполняется всегда (опционально)
  • try: блок, где мы «пробуем» выполнить потенциально опасный код.
  • except: реакция на конкретный тип ошибки. Можно ставить несколько except подряд для разных типов.
  • else: выполняется только если в try не возникло исключений. Удобно, чтобы отделить «опасный» код от «последующего безопасного».
  • finally: выполняется в любом случае — и при ошибке, и без неё. Чаще всего сюда ставят очистку ресурсов: закрытие файлов, соединений и т. п.

Простой пример: деление на ноль

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

python
a = 10
b = 0
 
try:
    result = a / b
    print("Результат:", result)
except ZeroDivisionError:
    print("Нельзя делить на ноль!")

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


Обработка разных ошибок: несколько except

Часто бывает несколько возможных проблем. Например, пользователь вводит данные, и там может быть либо не число, либо деление на ноль.

python
num_str = input("Введите числитель: ")
den_str = input("Введите знаменатель: ")
 
try:
    numerator = int(num_str)
    denominator = int(den_str)
    result = numerator / denominator
    print("Результат деления:", result)
except ValueError:
    print("Пожалуйста, вводите целые числа.")
except ZeroDivisionError:
    print("Знаменатель не может быть нулём.")

Здесь ValueError ловит случаи, когда строка не превращается в число, а ZeroDivisionError — деление на ноль. Порядок важен: более специфичные исключения пишут раньше, общие — позже.


else: когда ошибок нет

Блок else полезен, чтобы разделить «рискованный» код и «продолжение», которое имеет смысл только при успехе.

python
x = 5
y = 2
 
try:
    z = x / y
except ZeroDivisionError:
    print("Ошибка деления")
else:
    # Этот блок выполнится только если деления не вызвало ошибку
    print(f"{x} / {y} = {z}")

Если бы произошла ошибка, else бы не запустился. Это помогает держать логику понятной: «если всё хорошо — делаем вот это».


finally: действие, которое нужно всегда

finally нужен для гарантированной очистки. Самый частый кейс — закрытие файла.

python
filename = "data.txt"
 
try:
    f = open(filename, "r")
    data = f.read()
    print("Данные:", data)
except FileNotFoundError:
    print("Файл не найден.")
finally:
    # Выполнится и при ошибке, и при успехе
    try:
        f.close()
    except NameError:
        # Если f вообще не создался (например, ошибка в open)
        pass

В реальной практике лучше использовать контекстный менеджер with, но пример выше показывает именно принцип: finally гарантирует, что ресурс будет освобождён.

Более современный и предпочтительный вариант:

python
filename = "data.txt"
 
try:
    with open(filename, "r") as f:
        data = f.read()
        print("Данные:", data)
except FileNotFoundError:
    print("Файл не найден.")
# finally тут не нужен: with сам закроет файл

Получение информации об ошибке

Иногда недостаточно просто сказать «что-то пошло не так». Полезно вывести детали.

python
value = "abc"
 
try:
    number = int(value)
except ValueError as e:
    print("Произошла ошибка преобразования:", e)

as e сохраняет объект исключения в переменную e, у которой есть сообщение об ошибке. Это удобно для логирования.


Ловим несколько типов ошибок одним except

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

python
try:
    x = int(input("Введите число: "))
    y = 10 / x
except (ValueError, ZeroDivisionError) as e:
    print("Проблема с вводом или вычислением:", e)

Это сокращает код, когда реакция на разные ошибки одинаковая.


Общий except: использовать осторожно

Можно написать except Exception: или даже except: — это поймает почти всё. Но злоупотреблять этим не стоит: так легко скрыть реальные проблемы (опечатки, логические ошибки).

python
try:
    do_something()
except Exception as e:
    print("Что-то пошло не так:", e)
    # Тут обычно логируют ошибку, а не просто печатают

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


Практический пример: чтение и обработка файла с валидацией

Допустим, у нас есть файл с числами, по одному в строке. Нужно прочитать, преобразовать в int, посчитать среднее и обработать возможные проблемы.

python
def read_and_average(filename):
    total = 0
    count = 0
 
    try:
        with open(filename, "r", encoding="utf-8") as f:
            for line_no, line in enumerate(f, start=1):
                line = line.strip()
                if not line:
                    continue
                try:
                    value = int(line)
                    total += value
                    count += 1
                except ValueError:
                    print(f"Строка {line_no}: не число — пропускаем: '{line}'")
 
        if count == 0:
            return None
 
        return total / count
 
    except FileNotFoundError:
        print(f"Файл '{filename}' не найден.")
        return None
    except PermissionError:
        print(f"Нет прав на чтение файла '{filename}'.")
        return None

Что здесь происходит:

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

Частые ошибки и советы

  • Не делайте пустой except:. Это скрывает всё подряд и усложняет отладку.
  • Ловите конкретные исключения, когда это возможно.
  • Используйте finally или with для гарантированного освобождения ресурсов.
  • Логируйте ошибки, а не только печатайте в консоль — в реальных проектах это критично.
  • Не используйте обработку исключений для нормальной логики (например, не делайте проверку существования файла через FileNotFoundError). Для этого есть отдельные функции вроде os.path.exists().

Добавить комментарий