# Исключения в Python: try, except, else, finally, raise и свои исключения

URL: https://skillaria.top/ru/guides/python/isklyucheniya-python

Исключение в Python — объект ошибки, который прерывает выполнение кода, пока его не перехватит блок try/except; else выполняется, если ошибки не было, а finally — всегда. Вызвать исключение самому можно инструкцией raise, например raise ValueError("сумма отрицательная"), а своё исключение создаётся классом-наследником Exception.

Исключение в Python — это объект, который сигнализирует об ошибке и прерывает обычный ход программы. Если его никто не перехватил, Python печатает traceback и завершает работу. Перехватывают исключения конструкцией `try/except`, а вызывают сами — инструкцией `raise`. Разберём, как это устроено, какие ошибки встречаются чаще всего и как не превратить обработку ошибок в их сокрытие.

## Что такое исключение в Python

Когда операция не может выполниться — деление на ноль, обращение к несуществующему ключу, превращение слова в число, — Python создаёт объект исключения и «выбрасывает» его. Выполнение текущей функции останавливается, исключение поднимается по цепочке вызовов, пока не встретит подходящий обработчик. Не встретило — программа падает.

```
>>> 10 / 0
Traceback (most recent call last):
  File "", line 1, in
ZeroDivisionError: division by zero
```

Кстати, ответ на частый вопрос: при делении на ноль в Python возникает исключение `ZeroDivisionError`. Это касается и `/`, и `//`, и `%`.

## Как читать traceback

Traceback (трейсбек) — отчёт о том, как программа дошла до ошибки. Читать его удобнее снизу вверх. Последняя строка — главное: тип исключения и сообщение. Выше — строка кода, где всё случилось, ещё выше — кто её вызвал, и так до точки входа.

```
Traceback (most recent call last):
  File "tracker.py", line 9, in
    total = parse_amount(row)
  File "tracker.py", line 4, in parse_amount
    return float(raw.strip())
           ~~~~~^^^^^^^^^^^^^
ValueError: could not convert string to float: 'сто'
```

С Python 3.11 под строкой кода рисуются значки `~~~^^^` (PEP 657): они показывают, какое именно выражение в строке сломалось. Это особенно помогает, когда в одной строке несколько вызовов подряд.

### Подсказки «Did you mean»

С Python 3.10 при опечатке в имени ошибка предлагает близкий вариант. В Python 3.15 подсказки стали умнее: интерпретатор знает привычки из других языков. Вызов `list.push` подскажет `append`, `"кофе".toUpperCase()` — метод `upper`. Подсказка заглядывает и во вложенные поля: если словарь лежит в атрибуте `totals` объекта, а вы вызвали `items()` у самого объекта, ошибка предложит `.totals.items`. Близкие имена теперь подсказывает и `delattr()`.

## Типы исключений в Python

Встроенных исключений десятки, но на старте вы чаще всего увидите эти:

| Исключение | Когда возникает | Пример |
| --- | --- | --- |

| `NameError` | Имя не определено, чаще всего опечатка | `print(totl)` |

| `TypeError` | Операция не подходит для этого типа | `"5" + 3` |

| `ValueError` | Тип верный, значение нет | `int("сто")` |

| `KeyError` | Нет такого ключа в словаре | `d["missing"]` |

| `IndexError` | Индекс за пределами списка | `[1, 2][5]` |

| `AttributeError` | У объекта нет метода или поля | `[].push(1)` |

| `ZeroDivisionError` | Деление на ноль | `1 / 0` |

| `FileNotFoundError` | Файл не найден | `open("nope.txt")` |

Отдельно стоит `SyntaxError` (и его частный случай `IndentationError`): он возникает ещё при разборе файла, до запуска кода, поэтому перехватить его в том же файле через `try` нельзя.

### Иерархия исключений

Исключения — это классы, и они выстроены в иерархию. На вершине — `BaseException`. От него наследуются `Exception` (почти все «обычные» ошибки) и несколько системных: `KeyboardInterrupt`, `SystemExit`, `GeneratorExit`.

```
BaseException
 ├── KeyboardInterrupt
 ├── SystemExit
 └── Exception
      ├── ArithmeticError
      │    └── ZeroDivisionError
      ├── LookupError
      │    ├── KeyError
      │    └── IndexError
      ├── OSError
      │    └── FileNotFoundError
      ├── TypeError
      └── ValueError
```

Иерархия важна при перехвате: `except LookupError` поймает и `KeyError`, и `IndexError`. А `KeyboardInterrupt` — это не ошибка в коде: так Python сообщает, что пользователь нажал Ctrl+C. Он не наследуется от `Exception` специально, чтобы `except Exception` не мешал прервать программу.

## Обработка исключений: try/except/else/finally

Код, который может упасть, кладут в блок `try`. В блоке `except` указывают тип ошибки и что с ней делать. Именно `except` — это блок, который в Python используется для обработки исключений.

```
def parse_amount(raw):
    try:
        value = float(raw)
    except ValueError as e:
        print(f"Пропускаю строку: {e}")
        return None
    else:
        return value          # выполняется, если ошибки не было
    finally:
        print("разбор завершён")   # выполняется всегда

print(parse_amount("250"))
```

Порядок вывода здесь — сначала «разбор завершён», потом `250.0`. `return` в блоке `else` запоминает значение, затем Python обязательно выполняет `finally` и только после этого возвращает результат. Поэтому `finally` надёжен для уборки: закрыть файл, отпустить соединение.

Если в `finally` написать свой `return`, он перекроет предыдущий и даже «проглотит» исключение. С Python 3.14 за такой код выдаётся `SyntaxWarning` (PEP 765) — не пишите `return` в `finally`.

### Несколько исключений сразу

Можно написать несколько блоков `except` под разные типы или перечислить типы в кортеже:

```
try:
    amount = int(data["amount"])
except KeyError:
    amount = 0
except (ValueError, TypeError) as e:
    print("Кривая сумма:", e)
    amount = 0
```

Блоки проверяются сверху вниз, срабатывает первый подходящий, поэтому более конкретные типы ставят выше общих. С Python 3.14 (PEP 758) несколько типов можно перечислить и без скобок — `except ValueError, TypeError:`, — но если есть `as`, скобки по-прежнему обязательны.

### Как вывести текст ошибки и узнать её тип

Объект исключения доступен через `as e`. `str(e)` даёт сообщение, `type(e).__name__` — имя типа:

```
try:
    int("сто")
except ValueError as e:
    print(type(e).__name__)   # ValueError
    print(e)                  # invalid literal for int() with base 10: 'сто'
```

## Модуль traceback: полный текст ошибки

Иногда ошибку нужно перехватить, но сохранить полный трейсбек — для лога или отчёта. Для этого есть модуль `traceback`:

```
import traceback

try:
    1 / 0
except ZeroDivisionError:
    traceback.print_exc()             # печатает трейсбек в stderr
    text = traceback.format_exc()     # тот же текст строкой
```

Если вы пишете логи через модуль `logging`, проще вызвать `logging.exception("не удалось посчитать")` внутри `except` — трейсбек добавится к сообщению автоматически.

## Python raise: как вызвать исключение

Инструкция `raise` выбрасывает исключение вручную. Так функция сообщает, что не может выполнить работу с такими данными:

```
def add_expense(amount):
    if amount < 0:
        raise ValueError(f"Сумма не может быть отрицательной: {amount}")
    ...

add_expense(-5)
# ValueError: Сумма не может быть отрицательной: -5
```

Python raise — это не функция, а инструкция, поэтому скобки после слова `raise` не нужны: в скобках передаются аргументы конструктору исключения. Выбирайте тип по смыслу: неверное значение — `ValueError`, неверный тип — `TypeError`.

### raise без аргументов и raise from

Внутри `except` голый `raise` пробрасывает текущее исключение дальше без изменений. Это нужно, когда вы хотите что-то записать в лог, но обработку оставить вызывающему коду.

Если вы заменяете одно исключение другим, используйте `raise ... from e` — исходная ошибка сохранится в цепочке, и в трейсбеке будет видно обе:

```
def load_config(path):
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError as e:
        raise RuntimeError(f"Нет конфига {path}") from e
```

### add_note: дописать контекст к ошибке

С Python 3.11 (PEP 678) к исключению можно добавить заметку методом `add_note()` и выбросить его дальше. Тип ошибки не меняется, а заметка печатается под сообщением:

```
def parse(raw, line_no):
    try:
        return float(raw)
    except ValueError as e:
        e.add_note(f"файл expenses.csv, строка {line_no}")
        raise

parse("сто", 17)
# ValueError: could not convert string to float: 'сто'
# файл expenses.csv, строка 17
```

## Как создать своё исключение в Python

Свои исключения нужны для ошибок предметной области: превышен бюджет, заказ уже оплачен, пользователь заблокирован. Достаточно класса-наследника `Exception`:

```
class BudgetExceeded(Exception):
    def __init__(self, category, limit, spent):
        super().__init__(f"{category}: лимит {limit}, потрачено {spent}")
        self.category = category
        self.limit = limit
        self.spent = spent

def check_budget(category, limit, spent):
    if spent > limit:
        raise BudgetExceeded(category, limit, spent)

try:
    check_budget("Еда", 10000, 12500)
except BudgetExceeded as e:
    print("Предупреждение:", e)   # Предупреждение: Еда: лимит 10000, потрачено 12500
```

Теперь вызывающий код ловит именно превышение бюджета и не путает его с опечатками. В большом проекте удобно завести общий базовый класс (`class TrackerError(Exception)`) и наследовать от него все свои исключения — тогда их можно перехватить одним `except TrackerError`. Наследуйтесь от `Exception`, а не от `BaseException`.

## Группы исключений и except*

Иногда ошибок сразу несколько — например, при проверке целого файла или в параллельных задачах `asyncio.TaskGroup`. С Python 3.11 их можно собрать в `ExceptionGroup`, а обрабатывать по типам через `except*`:

```
def check_all(rows):
    errors = []
    for r in rows:
        try:
            float(r)
        except ValueError as e:
            errors.append(e)
    if errors:
        raise ExceptionGroup("ошибки в файле", errors)

try:
    check_all(["1", "a", "b"])
except* ValueError as eg:
    print(f"Кривых строк: {len(eg.exceptions)}")   # Кривых строк: 2
```

## Python try except все ошибки: почему так делать нельзя

Самая вредная привычка — ловить всё подряд: `except: pass`. Программа «перестаёт падать», но на деле перестаёт сообщать о проблемах. Голый `except` ловит даже `KeyboardInterrupt` и `SystemExit`, так что программу не остановить по Ctrl+C. `except Exception: pass` чуть лучше, но тоже молча проглатывает опечатки в именах и ошибки логики.

Как корректно обрабатывать исключения в Python:

- ловите только тот тип, который умеете обработать: `except ValueError`, а не всё;
- держите блок `try` коротким — только строки, которые могут упасть именно так;
- если ловите широко (`except Exception`) на верхнем уровне программы, обязательно логируйте трейсбек;
- не используйте исключения для обычного ветвления там, где хватает проверки: `dict.get()` вместо `try/except KeyError` в простых случаях;
- для ресурсов предпочитайте `with` — он сам закроет файл даже при ошибке.

## Итог

Исключения в Python — нормальный механизм сообщать об ошибках, а не повод их прятать. Читайте traceback снизу вверх, перехватывайте конкретные типы в `try/except`, используйте `else` для кода «если всё прошло» и `finally` для уборки. Python raise позволяет сообщить об ошибке самому, `raise from` и `add_note` сохраняют контекст, а свои исключения наследуются от `Exception`. В курсе по Python эта тема разобрана на практике: трекер расходов перестаёт падать из-за одной кривой строки CSV и выражает превышение бюджета своим исключением.

## FAQ

**Чем ошибка отличается от исключения в Python?**

В разговорной речи это синонимы. Технически SyntaxError находится ещё при разборе кода, а остальные ошибки возникают во время выполнения в виде объектов-исключений, которые можно перехватить.

**Можно ли продолжить выполнение после исключения с того же места?**

Нет. После перехвата выполнение продолжается после блока try/except, а не со строки, где возникла ошибка. Если нужно обработать каждую строку файла, кладите try внутрь цикла.

**Что будет, если исключение возникло внутри except?**

Оно поднимется дальше, а исходное исключение сохранится в его атрибуте __context__. В трейсбеке появится фраза During handling of the above exception, another exception occurred.

**Как проверить в тестах, что функция выбрасывает исключение?**

В pytest используют конструкцию with pytest.raises(ValueError):, в unittest — self.assertRaises(ValueError). Тест пройдёт, только если внутри блока возникло исключение нужного типа.

**Что такое assert и чем он отличается от raise?**

assert условие, "сообщение" выбрасывает AssertionError, если условие ложно. Это инструмент для проверок при разработке: при запуске с флагом -O assert отключаются, поэтому проверять ввод пользователя нужно через if и raise.


---
Source: Skillaria.Top (https://skillaria.top)
