ШПАРГАЛКА

Исключения в 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.
Поддержать проект