
Обработка и генерация исключений в Python
Исключения в Python — это механизм для обработки ошибок и нестандартных ситуаций без «падения» программы. Правильная обработка исключений в Python повышает надёжность, улучшает логи и пользовательский опыт. В этой статье вы разберёте базовую конструкцию try except Python, блоки else и finally, перехват конкретных ошибок (ValueError, ZeroDivisionerror, FileNotFoundError), raise и raise from, пользовательские исключения, логирование traceback, а также лучшие практики и мини-практикум. 🚀
Зачем нужна обработка исключений
Исключения помогают превратить «аварию» в контролируемый сценарий: вместо аварийного завершения программа сообщает, что пошло не так, освобождает ресурсы и предлагает действие по восстановлению. В продакшене это критично для устойчивости сервисов, в учебных проектах — для понимания причин ошибок.
- Надёжность. Перехват исключений предотвращает крах приложения.
- Предсказуемость. Пользователь получает понятное сообщение вместо «стека» в консоли.
- Поддерживаемость. Централизованная обработка и логирование упрощают отладку.
# Пример: без обработки программа падает
def div(a, b):
return a / b
print(div(1, 0)) # ZeroDivisionerror
Что такое исключения: иерархия BaseException → Exception
В Python исключения — это объекты классов, образующих иерархию: вершина — BaseException, рабочая ветка — Exception. Бóльшую часть времени вы перехватываете именно подклассы Exception (а не системные: SystemExit, KeyboardInterrupt и т. п.).
- Exception — «нормальные» ошибки времени выполнения: ValueError, TypeError, FileNotFoundError, ZeroDivisionerror, KeyError и др.
- BaseException — базовый класс (его обычно не ловят, чтобы не скрыть системные сигналы).
# Проверяем тип исключения по иерархии
try:
int("NaN")
except ValueError as e:
print("Ошибка преобразования:", e)
Типы исключений: обзор частых ошибок
Полный список обширен, но регулярнее всего встречаются:
- ValueError — неверное значение при корректном типе (парсинг чисел, дат).
- ZeroDivisionerror — деление на ноль.
- FileNotFoundError — отсутствует файл/путь.
- TypeError — операция с объектом неподходящего типа.
- KeyError/IndexError — нет ключа/индекса.
- TimeoutError/Connectionerror — сетевые проблемы.
# Несколько типовых сценариев
try:
open("missing.txt")
except FileNotFoundError:
print("Файл не найден")
try:
{"a": 1}["b"]
except KeyError as e:
print("Нет ключа:", e)
Базовая конструкция try/except
Минимальная схема: «попробовать» выполнить блок и, если возникла ошибка, «перехватить» и обработать её.
# try/except — точечная обработка
def read_int():
raw = input("Введите число: ")
try:
return int(raw)
except ValueError:
print("Это не целое число, попробуйте ещё раз.")
return None
Не используйте пустой except: он ловит всё подряд и маскирует ошибки. Ловите конкретные классы исключений.
Перехват конкретных vs любых исключений
Конкретика улучшает безопасность и диагностируемость. Допустим иметь несколько except для разных веток обработки.
# Несколько веток per-исключение
def load_json(path):
import json
try:
with open(path, encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return {"error": "file_not_found"}
except json.JSONDecodeError as e:
return {"error": "bad_json", "msg": str(e)}
Шаблон «ловить всё» уместен лишь в верхнем уровне приложения — и то с обязательным логированием traceback и немедленным ре-raise или корректным завершением.
Блоки else и finally: когда они нужны
else выполняется, если в try не было исключений — удобно для кода «после успешной попытки». finally выполняется всегда: для освобождения ресурсов (файлы, соединения).
# try/except/else/finally — полный шаблон
def safe_div(a, b):
try:
res = a / b
except ZeroDivisionerror:
return None
else:
return res
finally:
print("Готово: освобождение ресурсов/лог")
Для файлов и сетевых соединений используйте контекстные менеджеры with — они скрывают try/finally внутри.
# Контекстный менеджер сам закрывает файл
with open("data.txt", encoding="utf-8") as f:
print(f.read())
Продвинутая схема: вложенные try/except и finally
Иногда нужно разделить «узкий» перехват (ближе к источнику проблемы) и «широкий» (на уровень выше). Вложенные блоки дают гибкость, но не усложняйте без необходимости.
# Вложенный перехват локальной ошибки чтения
def process_file(path):
try:
with open(path, encoding="utf-8") as f:
try:
raw = f.read()
except UnicodeDecodeError:
return {"error": "encoding"}
except FileNotFoundError:
return {"error": "file_not_found"}
return {"ok": len(raw)}
Генерация исключений: raise и raise from
Иногда ошибку нужно сгенерировать намеренно: валидация входных данных, guard-условия, недостижимые ветки. Используйте информативные сообщения, помогающие локализовать проблему.
# Явный выброс исключения
def set_age(age):
if not (0 <= age <= 120):
raise ValueError(f"age out of range: {age}")
return age
raise from связывает исходную причину с новой («оборачивание»), сохраняя контекст для отладки.
def load_conf(path):
import json
try:
with open(path, encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError as e:
raise RuntimeError("config missing") from e
Пользовательские исключения
Создавайте собственные классы исключений, наследуясь от Exception. Это улучшает точность перехвата и делает код само-документируемым.
# Свой класс исключения
class ConfigError(Exception):
pass
def read_token(env):
token = env.get("TOKEN")
if not token:
raise ConfigError("TOKEN is not set")
return token
Иерархия пользовательских исключений позволяет группировать родственные ошибки модуля и ловить их одной веткой.
Логирование исключений и traceback
Журналы — ключ к быстрой диагностике. Используйте logging.exception, чтобы записать стек вызовов (traceback) автоматически.
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
def run():
try:
1/0
except ZeroDivisionerror:
logging.exception("divide by zero") # пишет traceback
На верхнем уровне приложения перехватывайте «неожиданное», логируйте и корректно завершайте процесс либо перезапускайте воркер — но не скрывайте ошибки молча.
Когда уместны исключения: проектирование и антипаттерны
- Уместно: исключительные ситуации, ошибки валидации, недоступные ресурсы, нарушенные инварианты.
- Неуместно: обычный контроль потока (например, «выйти из цикла»), редкие ветки ради «экономии» if.
- Плохие практики: пустой except:, глушение ошибок, ловить Exception слишком рано, мутировать состояние в except без аккуратной компенсации.
# Плохой приём: «проглотили» любую ошибку
try:
dangerous()
except Exception:
pass # ни логов, ни реакции — так нельзя
Чек-лист лучших практик
- Ловите конкретные исключения; общий перехват — только на самом верху и с логами.
- Используйте try/except/else/finally по назначению; очистку ресурсов выносите в finally или with.
- Сообщения raise делайте информативными: параметры, контекст.
- Для адаптации слоя «инфра → домен» применяйте raise from (перевод причины).
- Не злоупотребляйте вложенностью: выносите обработку в функции-утилиты.
- Логируйте traceback при неожиданных ошибках; не скрывайте критичные исключения.
Мини-практикум: 5 задач для закрепления
1) Безопасное чтение int с повтором
def read_int(prompt="Число: "):
while True:
try:
return int(input(prompt))
except ValueError:
print("Введите целое число!")
2) Обёртка с retry и исключениями
import time, random
def retry(times=3, delay=0.2):
def deco(fn):
def wrap(*a, **kw):
for i in range(times):
try:
return fn(*a, **kw)
except Exception as e:
if i+1 == times: raise
time.sleep(delay)
return wrap
return deco
@retry()
def flaky():
if random.random() < 0.7: raise Connectionerror("unstable")
return "ok"
3) Свои исключения для конфигурации
class EnvError(Exception): pass
def get_env(env, key):
val = env.get(key)
if not val: raise EnvError(f"missing {key}")
return val
4) Нормализация входных данных с raise from
def to_float(x):
try:
return float(x)
except (TypeError, ValueError) as e:
raise ValueError(f"cannot convert {x!r} to float") from e
5) Гарантированная очистка ресурсов
def write_log(path, text):
f = open(path, "a", encoding="utf-8")
try:
f.write(text + "\n")
finally:
f.close()
FAQ
Короткие ответы на частые вопросы по исключениям — раскройте нужный спойлер 👇
Только на самом верхнем уровне приложения и обязательно с логированием traceback и корректной реакцией (алерт, завершение, перезапуск). В рабочих функциях ловите конкретные классы — так безопаснее и понятнее.
except Exception не поймает системные сигналы (KeyboardInterrupt, SystemExit), а пустой except: поймает всё, включая их — это опасно. Почти всегда избегайте пустого except.
finally — универсален для любых завершающих действий; with — удобная обёртка для ресурсов, у которых есть протокол контекста (файлы, блокировки). Если доступен with — используйте его: код чище, меньше рисков забыть закрыть ресурс.
Чтобы сохранить связь между исходной и новой ошибкой. Это улучшает отладку и логи, особенно когда вы «поднимаете» ошибку с уровня инфраструктуры на доменный уровень.
Если у вашего модуля есть типовые ошибки, которые удобно ловить отдельно от прочих (например, проблемы конфигурации, бизнес-валидация). Наследуйтесь от Exception и группируйте исключения по иерархии.
Используйте logging.exception (в блоке except) — он автоматически записывает traceback. Не замещайте логирование простым print.
В Python, если нет return, возвращается None. Проверьте ветки в try/except: возможно, вы выводите сообщение, но забыли вернуть значение.
Заключение и полезные ссылки
Вы освоили try except Python, блоки else и finally, практики raise и raise from, а также пользовательские исключения Python и логирование traceback. Дальше — закрепляйте техники на задачах, выносите общие обработчики в утилиты, а в продакшене добавляйте структурные логи и мониторинг. Чем точнее вы проектируете обработку ошибок, тем устойчивее и понятнее ваш код 🙂
- Официальная документация Python: раздел об исключениях, иерархии и обработке.
- logging и traceback: руководство по ведению журналов и печати стеков.
- PEP 8: стиль сообщений и базовые рекомендации по читаемости.