# Обработка исключений в Python

При возникновении ошибки времени выполнения Python создает (возбуждает) специальный объект - исключение, который позволяет однозначно характеризовать возникшую ошибочную ситуацию. Выбор подходящего исключения происходит из встроенной [иерархии классов-исключений](https://docs.python.org/3/library/exceptions.html#exception-hierarchy) (фрагмент):

* `BaseException` (базовое исключение)
  * `SystemExit` (исключение, порождаемое функцией [`sys.exit()`](https://www.yuripetrov.ru/edu/python/ch_11_01.html#sys.exit) при выходе из программы)
  * `KeyboardInterrupt` (прерывании программы пользователем, `Ctrl+C`)
  * `Exception` (базовое несистемное исключение)
    * `ArithmeticError` (арифметическая ошибка)
      * `FloatingPointError` (неудачное выполнение операции с плавающей запятой)
      * `OverﬂowError` (результат арифметической операции слишком велик для представления)
      * `ZeroDivisionError` (деление на ноль)
    * `LookupError` (некорректный индекс или ключ)
      * `IndexError` (индекс не входит в диапазон элементов)
      * `KeyError` (несуществующий ключ)
    * `MemoryError` (недостаточно памяти)
    * `NameError` (не найдено переменной с таким именем)
    * `OSError` (ошибка, связанная с ОС - есть подклассы, например `FileNotFoundError`)
    * `SyntaxError` (синтаксическая ошибка, включает классы `IndentationError` и `TabError`)
    * `SystemError` (внутренняя ошибка)
    * `TypeError` (операция применена к объекту несоответствующего типа)
    * `ValueError` (аргумент правильного типа, но некорректного значения)

Пример встроенного возбуждения исключения:

```
>>> "я - строка" / 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'int'
```

### Конструкция `try`

Придерживаясь идеологии «Легче попросить прощения, чем разрешения», Python предусматривает конструкцию [`try`](https://www.yuripetrov.ru/edu/python/ch_07_01.html#id0) для обработки возникающих исключений.

`tryexceptelsefinally`

```python
try:                              # (try строго 1)
    try_ suite                    # код, который может выполниться с ошибкой
except exception_group1 as var1:  # (except - 0 (если есть finally) и более)
    except_suite1                 # код, выполняемый в случае исключения 'exception_group1'
...                               # ссылка на исключение может быть записана в 'var1'
except exception_groupN as varN:
    except_suiteN                 # код, выполняемый в случае исключения 'exception_groupN'
...                               # except-блоков может быть произвольное кол-во
else:                             # (else - 0 или 1)
    else_suite                    # выполняется, если try не завершен преждевременно (например, break)
finally:                          # (finally - 0 или 1)
    finally_suite                 # код, который должен выполнится всегда (была ошибка выше или нет)
```

Ход выполнения:

* код, который потенциально может привести к ошибке, помещается в блок [`try`](https://www.yuripetrov.ru/edu/python/ch_07_01.html#id0);
* в случае ошибки, код немедленно завершается и переходит в обработчик [`except`](https://www.yuripetrov.ru/edu/python/ch_07_01.html#except) (если он указан для соответствующего исключения);
* после поток выполнения переходит к [`else`](https://www.yuripetrov.ru/edu/python/ch_07_01.html#else) (если исключений не было) и [`finally`](https://www.yuripetrov.ru/edu/python/ch_07_01.html#finally) (в любом случае).

**Наиболее простой способ обработки исключений**

```python
try:
    x = int(input("Введите целое число x (для вычисления 1/x): "))
    res = 1 / x

    print("1/{} = {:.2f}".format(x, res))
except:
    print("Произошла ошибка!")

# --------------
# Примеры вывода:

# Введите целое число x (для вычисления 1/x): 3
# 1/3 = 0.33

# Введите целое число x (для вычисления 1/x): qwerty
# Произошла ошибка!
```

Подобный вариант обработки исключений не рекомендуется, т.к. блок [`except`](https://www.yuripetrov.ru/edu/python/ch_07_01.html#except) будет перехватывать любое исключение, что не позволит точно определить ошибку в коде. Улучшить код можно, добавив обработку исключения по классу (Листинг 7.1.4).

**Обработка общего класса исключений** `Exception`

```python
try:
    x = int(input("Введите целое число x (для вычисления 1/x): "))
    res = 1 / x

    print("1/{} = {:.2f}".format(x, res))
except Exception as err:
    print("Произошла ошибка!")
    print("Тип:", type(err))
    print("Описание:", err)

# --------------
# Примеры вывода:

# Введите целое число x (для вычисления 1/x): 3
# 1/3 = 0.33

# Введите целое число x (для вычисления 1/x): 5.5
# Произошла ошибка!
# Тип: <class 'ValueError'>
# Описание: invalid literal for int() with base 10: '5.5'
```

Рекомендуемым способом обработки исключений является как можно большая конкретизация класса исключения.

**Обработка конкретных классов исключений**

```python
try:
    x = int(input("Введите целое число x (для вычисления 1/x): "))
    res = 1 / x

    print("1/{} = {:.2f}".format(x, res))
except ZeroDivisionError:
    print("На ноль делить нельзя!")
except ValueError as err:  # 'err' содержит ссылку на исключение
    print("Будьте внимательны:", err)
except (FileExistsError, FileNotFoundError):  # Исключения можно перечислять в виде кортежа
    print("Этого никогда не случится - мы не работаем с файлами")
except Exception as err:
    # Все, что не обработано выше и является потомком 'Exception',
    # будет обработано здесь
    print("Произошла ошибка!")
    print("Тип:", type(err))
    print("Описание:", err)

# --------------
# Примеры вывода:

# Введите целое число x (для вычисления 1/x): 3
# 1/3 = 0.33

# Введите целое число x (для вычисления 1/x): 0
# На ноль делить нельзя!

# Введите целое число x (для вычисления 1/x): qwerty
# Будьте внимательны: invalid literal for int() with base 10: 'qwerty'
```

### Возбуждение исключений (`raise`)

Исключения являются не только механизмом обработки ошибок, но и удобным средством управления потоком выполнения. Так, необходимое исключение можно возбудить вручную, когда это необходимо, используя конструкцию [`raise`](https://www.yuripetrov.ru/edu/python/ch_07_01.html#id45).

`raise`

```python
raise exception(args)  # явное указание класса возбуждаемого исключения

# или

raise                  # 1) повторное возбуждение активного исключения (re-raise)
                       #    внутри блока except
                       # 2) 'TypeError' по умолчанию
```

Возбуждаемое исключение может быть как встроенным (если соответствует по смыслу), так и пользовательским (создаваемым самостоятельно).

В Листинге 7.1.6 приведен пример использование оператора [`raise`](https://www.yuripetrov.ru/edu/python/ch_07_01.html#id45).

Листинг 7.1.6 - Использование [`raise`](https://www.yuripetrov.ru/edu/python/ch_07_01.html#id45) для управления потоком выполнения | [`скачать`](https://www.yuripetrov.ru/edu/python/_downloads/e18006b8dcbfcb08b1bf8320d48c34ee/07_01_06.py)

```python
MIN = 1
MAX = 10

try:
    x = int(input("Введите целое число от {} до {}: ".format(MIN, MAX)))

    if not MIN <= x <= MAX:
        # Возбудив исключение, его можно будет обработать в except
        # вместе с другими похожими исключениями
        raise ValueError("Число лежит вне интервала [{}; {}]!".format(MIN, MAX))

    print("Спасибо!")
except ValueError as err:  # 'err' содержит ссылку на исключение
    print("Будьте внимательны:", err)

# --------------
# Примеры вывода:

# Введите целое число от 1 до 10: 5
# Спасибо!

# Введите целое число от 1 до 10: 15
# Будьте внимательны: Число лежит вне интервала [1; 10]!

# Введите целое число от 1 до 10: qwerty
# Будьте внимательны: invalid literal for int() with base 10: 'qwerty'
```

### Особенности обработки исключений внутри функций

Обработка исключений аналогично производится и внутри функций, однако, необходимо писать код так, чтобы вызывающий код знал о случившемся, если это влияет на его дальнейшую работу.

Разница в обработке исключений приведена в Листинге 7.1.7.

Листинг 7.1.7 - Различная обработка исключений в функции | [`скачать`](https://www.yuripetrov.ru/edu/python/_downloads/3eaa2e8a5409f91d6726c6a5da8f262f/07_01_07.py)

```python
# Ниже представлены 3 варианта обработки исключений в функциях
# Основное правило - обработка исключений внутри возможна и нужна, однако
#                    вызывающий код должен также знать о случившемся, если
#                    влияет на дальнейшую работу


def get_1_x(x):
    """Вернуть 1/x.

    Функция не обрабатывает исключения - ответственность на вызывающем коде.
    """
    return 1/x


def get_2_x(x):
    """Вернуть 2/x.

    Функция обрабатывает исключения, "затушив" ошибку - вызывающий код
    не будет знать, сработала функция правильно или нет.

    Данный способ использовать не рекомендуется!
    """
    try:
        return 2/x
    except Exception as e:
        print("Внутри произошла ошибка...", e)


def get_3_x(x):
    """Вернуть 3/x.

    Функция не только обрабатывает исключения, но перевозбуждает его:
    в результате вызывающий так же получает возникшее исключение.

    Внутренняя обработка исключений может быть полезна, если в целом результат
    функции не связан с внутренней ошибкой.
    """
    try:
        return 3/x
    except Exception as e:
        print("Внутри произошла ошибка...", e)
        raise

funcs = (get_1_x, get_2_x, get_3_x)
# Вызываем каждую функцию с "ошибочным" параметром
for func in funcs:
    try:
        print("-" * 50)
        print("Запущена функция:", func.__name__)
        print(func(0))
    except Exception as e:
        print("Произошла ошибка: {}.".format(e))

# -------------
# Пример вывода:

# --------------------------------------------------
# Запущена функция: get_1_x
# Произошла ошибка: division by zero.
# --------------------------------------------------
# Запущена функция: get_2_x
# Внутри произошла ошибка... division by zero
# None
# --------------------------------------------------
# Запущена функция: get_3_x
# Внутри произошла ошибка... division by zero
# Произошла ошибка: division by zero.
```

\


[https://www.yuripetrov.ru/edu/python/ch\_07\_01.html](https://www.yuripetrov.ru/edu/python/ch_07_01.html) (TOR)\
