Исключения в Python
Шпаргалка по исключениям в Python: try/except/else/finally, raise, свои классы ошибок, иерархия встроенных исключений, with и assert.
Исключения (exceptions) — это объекты-сигналы об ошибках, которые прерывают обычный ход программы. Грамотная обработка исключений делает код устойчивым: вместо аварийного завершения вы перехватываете проблему и решаете, что с ней делать. В этой шпаргалке собраны основные конструкции работы с ошибками в Python.
Базовый try / except
Код, который может выбросить ошибку, помещают в блок try. Если внутри возникает исключение, выполнение прыгает в подходящий блок except.
try:
x = int("abc") # выбросит ValueError
except ValueError:
print("Не число!")
# Не число!
Без try/except та же строка завершила бы программу с трейсбеком ValueError: invalid literal for int().
try / except / else / finally
Полная форма состоит из четырёх блоков. else выполняется, только если исключений не было. finally выполняется всегда — и при ошибке, и без неё (удобно для освобождения ресурсов).
try:
value = 10 / 2
except ZeroDivisionError:
print("Деление на ноль")
else:
print("Успех:", value) # выполнится, ошибок нет
finally:
print("Готово")
# Успех: 5.0
# Готово
Порядок исполнения: try → (при ошибке) except или (без ошибки) else → и в любом случае finally.
Перехват конкретного исключения
Старайтесь ловить конкретный тип ошибки, а не «всё подряд». Голый except: или except Exception: может замаскировать опечатки и неожиданные баги.
data = {"name": "Аня"}
try:
print(data["age"]) # ключа нет
except KeyError:
print("Ключ не найден")
# Ключ не найден
Несколько блоков except
Можно перечислить несколько except подряд — Python проверяет их сверху вниз и выполняет первый подходящий.
def parse(s):
try:
return 100 / int(s)
except ValueError:
return "Это не число"
except ZeroDivisionError:
return "Ноль недопустим"
print(parse("abc")) # Это не число
print(parse("0")) # Ноль недопустим
print(parse("4")) # 25.0
Важно: более общие исключения ставьте ниже более конкретных. Если except Exception поставить первым, он перехватит всё, и специализированные блоки никогда не сработают.
Кортеж исключений в одном except
Если на разные ошибки нужна одинаковая реакция, перечислите их типы кортежем в скобках.
try:
result = int(input_value) / divisor
except (ValueError, TypeError, ZeroDivisionError):
print("Некорректные входные данные")
Доступ к объекту ошибки через as
Через as e вы получаете сам объект исключения: можно прочитать текст сообщения (str(e)), аргументы (e.args) и тип.
try:
int("xyz")
except ValueError as e:
print("Сообщение:", e)
print("Тип:", type(e).__name__)
print("Аргументы:", e.args)
# Сообщение: invalid literal for int() with base 10: 'xyz'
# Тип: ValueError
# Аргументы: ("invalid literal for int() with base 10: 'xyz'",)
Возбуждение исключений: raise
Оператор raise выбрасывает исключение вручную — например, при проверке аргументов.
def set_age(age):
if age < 0:
raise ValueError(f"Возраст не может быть отрицательным: {age}")
return age
set_age(-5)
# ValueError: Возраст не может быть отрицательным: -5
Повторный проброс
Внутри except голый raise (без аргументов) пробрасывает текущее исключение дальше — удобно, чтобы залогировать и не «съесть» ошибку.
try:
risky_operation()
except ValueError:
log("Поймали ValueError")
raise # пробрасываем ту же ошибку выше
Можно связать причины через raise ... from ..., сохранив исходное исключение в трейсбеке:
try:
config = data["settings"]
except KeyError as e:
raise RuntimeError("Конфиг повреждён") from e
Свои классы исключений
Собственные исключения наследуют от Exception (а не от BaseException). Это позволяет отделять «свои» ошибки приложения от встроенных.
class InsufficientFundsError(Exception):
"""Недостаточно средств на счёте."""
pass
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(
f"Нужно {amount}, доступно {self.balance}"
)
self.balance -= amount
account = BankAccount(100)
try:
account.withdraw(150)
except InsufficientFundsError as e:
print("Ошибка:", e)
# Ошибка: Нужно 150, доступно 100
Можно добавлять собственные атрибуты, передавая их в __init__:
class ValidationError(Exception):
def __init__(self, field, message):
self.field = field
super().__init__(f"[{field}] {message}")
try:
raise ValidationError("email", "неверный формат")
except ValidationError as e:
print(e.field, "->", e)
# email -> [email] неверный формат
Иерархия встроенных исключений
Все встроенные исключения наследуются от BaseException. Почти все «обычные» ошибки — потомки Exception. Перехватывая родительский класс, вы ловите и всех его потомков (например, except LookupError поймает и KeyError, и IndexError).
| Исключение | Когда возникает | Пример |
|---|---|---|
ValueError | Правильный тип, но недопустимое значение | int("abc") |
TypeError | Операция над несовместимым типом | "2" + 2 |
KeyError | Нет такого ключа в словаре | {}["x"] |
IndexError | Индекс за пределами последовательности | [1, 2][5] |
FileNotFoundError | Файл не существует | open("нет.txt") |
ZeroDivisionError | Деление на ноль | 1 / 0 |
AttributeError | Нет такого атрибута у объекта | None.foo |
NameError | Имя не определено | print(undefined) |
ImportError | Не удалось импортировать модуль | import нетмодуля |
StopIteration | У итератора кончились элементы | next(iter([])) |
RuntimeError | Общая ошибка времени выполнения | — |
NotImplementedError | Метод обязан быть переопределён | заглушка в базовом классе |
OSError | Ошибка ОС (файлы, сеть) | родитель FileNotFoundError |
KeyboardInterrupt | Прерывание с клавиатуры (Ctrl+C) | наследник BaseException |
Упрощённо иерархия выглядит так:
BaseException
├── KeyboardInterrupt
├── SystemExit
└── Exception
├── ArithmeticError
│ └── ZeroDivisionError
├── LookupError
│ ├── KeyError
│ └── IndexError
├── ValueError
├── TypeError
├── AttributeError
├── NameError
└── OSError
└── FileNotFoundError
Совет: ловите except Exception, но не except BaseException — иначе перехватите даже KeyboardInterrupt и SystemExit, и программу нельзя будет нормально остановить.
Контекстные менеджеры: with
Оператор with гарантирует, что ресурс будет корректно закрыт даже при исключении — это аналог try/finally, но короче и безопаснее. Чаще всего применяется к файлам.
# Без with пришлось бы вручную звать f.close() в finally
with open("data.txt", "r", encoding="utf-8") as f:
content = f.read()
# файл автоматически закрыт здесь, даже если read() упал
Под капотом with вызывает методы __enter__ и __exit__. Свой контекстный менеджер можно сделать через эти методы или через декоратор contextlib.contextmanager:
from contextlib import contextmanager
@contextmanager
def timer(label):
import time
start = time.time()
try:
yield # тело with выполняется здесь
finally:
print(f"{label}: {time.time() - start:.3f}с")
with timer("загрузка"):
sum(range(1_000_000))
# загрузка: 0.012с
Проверки через assert
assert условие, сообщение возбуждает AssertionError, если условие ложно. Это инструмент для отладки и проверки инвариантов, а не для валидации пользовательского ввода.
def average(numbers):
assert len(numbers) > 0, "Список не может быть пустым"
return sum(numbers) / len(numbers)
average([])
# AssertionError: Список не может быть пустым
Внимание: при запуске Python с флагом оптимизации python -O все assert отключаются. Поэтому никогда не полагайтесь на assert для проверок, критичных к безопасности — для них используйте if ... raise.
Краткие правила
- Ловите конкретные исключения, а не
except Exception«на всякий случай». finallyиwith— для гарантированного освобождения ресурсов.- Используйте
raise ... from ..., чтобы не терять исходную причину ошибки. - Свои исключения наследуйте от
Exception, давайте им осмысленные имена. assert— для отладки, для валидации ввода беритеif ... raise.