Когда программа делает что-то с внешними данными (читает файл, делит числа, получает ввод от пользователя), всегда есть риск, что что-то пойдёт не так: файл не найдётся, на ноль делить нельзя, а пользователь вместо числа введёт буквы. В Python для работы с такими ситуациями используют конструкцию try/except/finally. Её задача — не дать программе упасть с непонятной ошибкой, а обработать проблему аккуратно и, если нужно, продолжить работу.
Как устроена конструкция
Базовая схема выглядит так:
try: # код, который может вызвать ошибку except SomeException: # что делать, если ошибка этого типа случилась else: # выполняется, если ошибок не было (опционально) finally: # выполняется всегда (опционально)
try: блок, где мы «пробуем» выполнить потенциально опасный код.except: реакция на конкретный тип ошибки. Можно ставить несколькоexceptподряд для разных типов.else: выполняется только если вtryне возникло исключений. Удобно, чтобы отделить «опасный» код от «последующего безопасного».finally: выполняется в любом случае — и при ошибке, и без неё. Чаще всего сюда ставят очистку ресурсов: закрытие файлов, соединений и т. п.
Простой пример: деление на ноль
Самая частая учебная ошибка — деление на ноль, которое вызывает ZeroDivisionError.
a = 10 b = 0 try: result = a / b print("Результат:", result) except ZeroDivisionError: print("Нельзя делить на ноль!")
Программа не падает, а выводит понятное сообщение. Это уже лучше, чем страшная трассировка стека в консоль.
Обработка разных ошибок: несколько except
Часто бывает несколько возможных проблем. Например, пользователь вводит данные, и там может быть либо не число, либо деление на ноль.
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 полезен, чтобы разделить «рискованный» код и «продолжение», которое имеет смысл только при успехе.
x = 5 y = 2 try: z = x / y except ZeroDivisionError: print("Ошибка деления") else: # Этот блок выполнится только если деления не вызвало ошибку print(f"{x} / {y} = {z}")
Если бы произошла ошибка, else бы не запустился. Это помогает держать логику понятной: «если всё хорошо — делаем вот это».
finally: действие, которое нужно всегда
finally нужен для гарантированной очистки. Самый частый кейс — закрытие файла.
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 гарантирует, что ресурс будет освобождён.
Более современный и предпочтительный вариант:
filename = "data.txt" try: with open(filename, "r") as f: data = f.read() print("Данные:", data) except FileNotFoundError: print("Файл не найден.") # finally тут не нужен: with сам закроет файл
Получение информации об ошибке
Иногда недостаточно просто сказать «что-то пошло не так». Полезно вывести детали.
value = "abc" try: number = int(value) except ValueError as e: print("Произошла ошибка преобразования:", e)
as e сохраняет объект исключения в переменную e, у которой есть сообщение об ошибке. Это удобно для логирования.
Ловим несколько типов ошибок одним except
Можно перехватывать сразу несколько типов исключений:
try: x = int(input("Введите число: ")) y = 10 / x except (ValueError, ZeroDivisionError) as e: print("Проблема с вводом или вычислением:", e)
Это сокращает код, когда реакция на разные ошибки одинаковая.
Общий except: использовать осторожно
Можно написать except Exception: или даже except: — это поймает почти всё. Но злоупотреблять этим не стоит: так легко скрыть реальные проблемы (опечатки, логические ошибки).
try: do_something() except Exception as e: print("Что-то пошло не так:", e) # Тут обычно логируют ошибку, а не просто печатают
Используйте общий except только когда точно понимаете, зачем он нужен, и обязательно логируйте ошибку, чтобы потом разобраться.
Практический пример: чтение и обработка файла с валидацией
Допустим, у нас есть файл с числами, по одному в строке. Нужно прочитать, преобразовать в int, посчитать среднее и обработать возможные проблемы.
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().
