Try except python все ошибки

Исключения (exceptions) — ещё один тип данных в python. Исключения необходимы для того, чтобы сообщать программисту об ошибках.

Самый простейший пример исключения — деление на ноль:

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

Разберём это сообщение подробнее: интерпретатор нам сообщает о том, что он поймал исключение и напечатал информацию (Traceback (most recent call last)).

Далее имя файла (File «»). Имя пустое, потому что мы находимся в интерактивном режиме, строка в файле (line 1);

Выражение, в котором произошла ошибка (100 / 0).

Название исключения (ZeroDivisionError) и краткое описание исключения (division by zero).

Разумеется, возможны и другие исключения:

>>> 2 + '1'
Traceback (most recent call last):
  File "", line 1, in
    2 + '1'
TypeError: unsupported operand type(s) for +: 'int' and 'str'

>>> int('qwerty')
Traceback (most recent call last):
  File "", line 1, in
    int('qwerty')
ValueError: invalid literal for int() with base 10: 'qwerty'

В этих двух примерах генерируются исключения TypeError и ValueError соответственно. Подсказки дают нам полную информацию о том, где порождено исключение, и с чем оно связано.

Рассмотрим иерархию встроенных в python исключений, хотя иногда вам могут встретиться и другие, так как программисты могут создавать собственные исключения. Данный список актуален для python 3.3, в более ранних версиях есть незначительные изменения.

  • BaseException — базовое исключение, от которого берут начало все остальные.
    • SystemExit — исключение, порождаемое функцией sys.exit при выходе из программы.
    • KeyboardInterrupt — порождается при прерывании программы пользователем (обычно сочетанием клавиш Ctrl+C).
    • GeneratorExit — порождается при вызове метода close объекта generator.
    • Exception — а вот тут уже заканчиваются полностью системные исключения (которые лучше не трогать) и начинаются обыкновенные, с которыми можно работать.
      • StopIteration — порождается встроенной функцией next, если в итераторе больше нет элементов.
      • ArithmeticError — арифметическая ошибка.
        • FloatingPointError — порождается при неудачном выполнении операции с плавающей запятой. На практике встречается нечасто.
        • OverflowError — возникает, когда результат арифметической операции слишком велик для представления. Не появляется при обычной работе с целыми числами (так как python поддерживает длинные числа), но может возникать в некоторых других случаях.
        • ZeroDivisionError — деление на ноль.
      • AssertionError — выражение в функции assert ложно.
      • AttributeError — объект не имеет данного атрибута (значения или метода).
      • BufferError — операция, связанная с буфером, не может быть выполнена.
      • EOFError — функция наткнулась на конец файла и не смогла прочитать то, что хотела.
      • ImportError — не удалось импортирование модуля или его атрибута.
      • LookupError — некорректный индекс или ключ.
        • IndexError — индекс не входит в диапазон элементов.
        • KeyError — несуществующий ключ (в словаре, множестве или другом объекте).
      • MemoryError — недостаточно памяти.
      • NameError — не найдено переменной с таким именем.
        • UnboundLocalError — сделана ссылка на локальную переменную в функции, но переменная не определена ранее.
      • OSError — ошибка, связанная с системой.
        • BlockingIOError
        • ChildProcessError — неудача при операции с дочерним процессом.
        • ConnectionError — базовый класс для исключений, связанных с подключениями.
          • BrokenPipeError
          • ConnectionAbortedError
          • ConnectionRefusedError
          • ConnectionResetError
        • FileExistsError — попытка создания файла или директории, которая уже существует.
        • FileNotFoundError — файл или директория не существует.
        • InterruptedError — системный вызов прерван входящим сигналом.
        • IsADirectoryError — ожидался файл, но это директория.
        • NotADirectoryError — ожидалась директория, но это файл.
        • PermissionError — не хватает прав доступа.
        • ProcessLookupError — указанного процесса не существует.
        • TimeoutError — закончилось время ожидания.
      • ReferenceError — попытка доступа к атрибуту со слабой ссылкой.
      • RuntimeError — возникает, когда исключение не попадает ни под одну из других категорий.
      • NotImplementedError — возникает, когда абстрактные методы класса требуют переопределения в дочерних классах.
      • SyntaxError — синтаксическая ошибка.
        • IndentationError — неправильные отступы.
          • TabError — смешивание в отступах табуляции и пробелов.
      • SystemError — внутренняя ошибка.
      • TypeError — операция применена к объекту несоответствующего типа.
      • ValueError — функция получает аргумент правильного типа, но некорректного значения.
      • UnicodeError — ошибка, связанная с кодированием / раскодированием unicode в строках.
        • UnicodeEncodeError — исключение, связанное с кодированием unicode.
        • UnicodeDecodeError — исключение, связанное с декодированием unicode.
        • UnicodeTranslateError — исключение, связанное с переводом unicode.
      • Warning — предупреждение.

Теперь, зная, когда и при каких обстоятельствах могут возникнуть исключения, мы можем их обрабатывать. Для обработки исключений используется конструкция try — except.

Первый пример применения этой конструкции:

>>> try:
...     k = 1 / 0
... except ZeroDivisionError:
...     k = 0
...
>>> print(k)
0

В блоке try мы выполняем инструкцию, которая может породить исключение, а в блоке except мы перехватываем их. При этом перехватываются как само исключение, так и его потомки. Например, перехватывая ArithmeticError, мы также перехватываем FloatingPointError, OverflowError и ZeroDivisionError.

>>> try:
...     k = 1 / 0
... except ArithmeticError:
...     k = 0
...
>>> print(k)
0

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

Ещё две инструкции, относящиеся к нашей проблеме, это finally и else. Finally выполняет блок инструкций в любом случае, было ли исключение, или нет (применима, когда нужно непременно что-то сделать, к примеру, закрыть файл). Инструкция else выполняется в том случае, если исключения не было.

>>> f = open('1.txt')
>>> ints = []
>>> try:
...     for line in f:
...         ints.append(int(line))
... except ValueError:
...     print('Это не число. Выходим.')
... except Exception:
...     print('Это что ещё такое?')
... else:
...     print('Всё хорошо.')
... finally:
...     f.close()
...     print('Я закрыл файл.')
...     # Именно в таком порядке: try, группа except, затем else, и только потом finally.
...
Это не число. Выходим.
Я закрыл файл.

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

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

Напишем программу, которая будет считать обратные значения для целых чисел из заданного диапазона и выводить их в одну строку с разделителем ‘;’. Один из вариантов кода для решения этой задачи выглядит так:

print(";".join(str(1 / x) for x in range(int(input()), int(input()) + 1)))

Программа получилась в одну строчку за счёт использования списочных выражений. Однако при вводе диапазона чисел, включающего в себя 0 (например, от -1 до 1), программа выдаст следующую ошибку:

ZeroDivisionError: division by zero

В программе произошла ошибка «деление на ноль». Такая ошибка, возникающая при выполнении программы и останавливающая её работу, называется исключением.

Попробуем в нашей программе избавиться от возникновения исключения деления на ноль. Пусть при попадании 0 в диапазон чисел обработка не производится и выводится сообщение «Диапазон чисел содержит 0». Для этого нужно проверить до списочного выражения наличие нуля в диапазоне:

interval = range(int(input()), int(input()) + 1)
if 0 in interval:
    print("Диапазон чисел содержит 0.")
else:
    print(";".join(str(1 / x) for x in interval))

Теперь для диапазона, включающего в себя 0, например от -2 до 2, исключения ZeroDivisionError не возникнет. Однако при вводе строки, которую невозможно преобразовать в целое число (например, «a»), будет вызвано другое исключение:

ValueError: invalid literal for int() with base 10: 'a'

Произошло исключение ValueError. Для борьбы с этой ошибкой нам придётся проверить, что строка состоит только из цифр. Сделать это нужно до преобразования в число. Тогда наша программа будет выглядеть так:

start = input()
end = input()
# Метод lstrip("-"), удаляющий символы "-" в начале строки, нужен для учёта
# отрицательных чисел, иначе isdigit() вернёт для них False
if not (start.lstrip("-").isdigit() and end.lstrip("-").isdigit()):
    print("
    ввести два числа.")
else:
    interval = range(int(start), int(end) + 1)
    if 0 in interval:
        print("Диапазон чисел содержит 0.")
    else:
        print(";".join(str(1 / x) for x in interval))

Теперь наша программа работает без ошибок и при вводе строк, которые нельзя преобразовать в целое число.

Подход, который был нами применён для предотвращения ошибок, называется Look Before You Leap (LBYL), или «Посмотри перед прыжком». В программе, реализующей такой подход, проверяются возможные условия возникновения ошибок до исполнения основного кода.

Подход LBYL имеет недостатки. Программу из примера стало сложнее читать из-за вложенного условного оператора. Проверка условия, что строка может быть преобразована в число, выглядит даже сложнее, чем списочное выражение. Вложенный условный оператор не решает поставленную задачу, а только лишь проверяет входные данные на корректность. Легко заметить, что решение основной задачи заняло меньше времени, чем составление условий проверки корректности входных данных.

Существует другой подход для работы с ошибками: Easier to Ask Forgiveness than Permission (EAFP), или «Проще попросить прощения, чем разрешения». В этом подходе сначала исполняется код, а в случае возникновения ошибок происходит их обработка. Подход EAFP реализован в Python в виде обработки исключений.

Исключения в Python являются классами ошибок. В Python есть много стандартных исключений. Они имеют определённую иерархию за счёт механизма наследования классов. В документации Python версии 3.10.8 приводится следующее дерево иерархии стандартных исключений:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      |    +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- EncodingWarning
           +-- ResourceWarning

Для обработки исключения в Python используется следующий синтаксис:

try:
    <код , который может вызвать исключения при выполнении>
except <классисключения_1>:
    <код обработки исключения>
except <классисключения_2>:
    <код обработки исключения>
...
else:
    <код выполняется, если не вызвано исключение в блоке try>
finally:
    <код , который выполняется всегда>

Блок try содержит код, в котором нужно обработать исключения, если они возникнут.
При возникновении исключения интерпретатор последовательно проверяет, в каком из блоков except обрабатывается это исключение.
Исключение обрабатывается в первом блоке except, обрабатывающем класс этого исключения или базовый класс возникшего исключения.
Необходимо учитывать иерархию исключений для определения порядка их обработки в блоках except. Начинать обработку исключений следует с более узких классов исключений. Если начать с более широкого класса исключения, например Exception, то всегда при возникновении исключения будет срабатывать первый блок except.
Сравните два следующих примера. В первом порядок обработки исключений указан от производных классов к базовым, а во втором — наоборот.

Первый пример:

try:
    print(1 / int(input()))
except ZeroDivisionError:
    print("Ошибка деления на ноль.")
except ValueError:
    print("Невозможно преобразовать строку в число.")
except Exception:
    print("Неизвестная ошибка.")

При вводе значений «0» и «a» получим ожидаемый, соответствующий возникающим исключениям вывод:

Невозможно преобразовать строку в число.

и

Ошибка деления на ноль.

Второй пример:

try:
    print(1 / int(input()))
except Exception:
    print("Неизвестная ошибка.")
except ZeroDivisionError:
    print("Ошибка деления на ноль.")
except ValueError:
    print("Невозможно преобразовать строку в число.")

При вводе значений «0» и «a» получим в обоих случаях неинформативный вывод:

Неизвестная ошибка.

Необязательный блок else выполняет код в случае, если в блоке try не вызвано исключение. Добавим блок else в пример для вывода сообщения об успешном выполнении операции:

try:
    print(1 / int(input()))
except ZeroDivisionError:
    print("Ошибка деления на ноль.")
except ValueError:
    print("Невозможно преобразовать строку в число.")
except Exception:
    print("Неизвестная ошибка.")
else:
    print("Операция выполнена успешно.")

Теперь при вводе корректного значения, например «5», вывод программы будет следующим:

2.0
Операция выполнена успешно.

Блок finally выполняется всегда, даже если возникло какое-то исключение, не учтённое в блоках except, или код в этих блоках сам вызвал какое-либо исключение. Добавим в нашу программу вывод строки «Программа завершена» в конце программы даже при возникновении исключений:

try:
    print(1 / int(input()))
except ZeroDivisionError:
    print("Ошибка деления на ноль.")
except ValueError:
    print("Невозможно преобразовать строку в число.")
except Exception:
    print("Неизвестная ошибка.")
else:
    print("Операция выполнена успешно.")
finally:
    print("Программа завершена.")

Перепишем код, созданный с применением подхода LBYL, для первого примера из этой главы с использованием обработки исключений:

try:
    print(";".join(str(1 / x) for x in range(int(input()), int(input()) + 1)))
except ZeroDivisionError:
    print("Диапазон чисел содержит 0.")
except ValueError:
    print("Необходимо ввести два числа.")

Теперь наша программа читается намного легче. При этом создание кода для обработки исключений не заняло много времени и не потребовало проверки сложных условий.

Исключения можно принудительно вызывать с помощью оператора raise. Этот оператор имеет следующий синтаксис:

raise <класс исключения>(параметры)

В качестве параметра можно, например, передать строку с сообщением об ошибке.

Создание собственных исключений

В Python можно создавать свои собственные исключения. Синтаксис создания исключения такой же, как и у создания класса. При создании исключения его необходимо наследовать от какого-либо стандартного класса-исключения.

Напишем программу, которая выводит сумму списка целых чисел и вызывает исключение, если в списке чисел есть хотя бы одно чётное или отрицательное число. Создадим свои классы исключений:

  • NumbersError — базовый класс исключения;
  • EvenError — исключение, которое вызывается при наличии хотя бы одного чётного числа;
  • NegativeError — исключение, которое вызывается при наличии хотя бы одного отрицательного числа.
class NumbersError(Exception):
    pass


class EvenError(NumbersError):
    pass


class NegativeError(NumbersError):
    pass


def no_even(numbers):
    if all(x % 2 != 0 for x in numbers):
        return True
    raise EvenError("В списке не должно быть чётных чисел")


def no_negative(numbers):
    if all(x >= 0 for x in numbers):
        return True
    raise NegativeError("В списке не должно быть отрицательных чисел")


def main():
    print("Введите числа в одну строку через пробел:")
    try:
        numbers = [int(x) for x in input().split()]
        if no_negative(numbers) and no_even(numbers):
            print(f"Сумма чисел равна: {sum(numbers)}.")
    except NumbersError as e:  # обращение к исключению как к объекту
        print(f"Произошла ошибка: {e}.")
    except Exception as e:
        print(f"Произошла непредвиденная ошибка: {e}.")

        
if __name__ == "__main__":
    main()

Модули

Обратите внимание: в программе основной код выделен в функцию main. А код вне функций содержит только условный оператор и вызов функции main при выполнении условия __name__ == "__main__". Это условие проверяет, запущен ли файл как самостоятельная программа или импортирован как модуль.

Любая программа, написанная на языке программирования Python, может быть импортирована как модуль в другую программу. В идеологии Python импортировать модуль — значит полностью его выполнить. Если основной код модуля содержит вызовы функций, ввод или вывод данных без использования указанного условия __name__ == "__main__", то произойдёт полноценный запуск программы. А это не всегда удобно, если из модуля нужна только отдельная функция или какой-либо класс.

При изучении модуля itertools мы говорили о том, как импортировать модуль в программу. Покажем ещё раз два способа импорта на примере собственного модуля.

Для импорта модуля из файла, например example_module.py, нужно указать его имя, если он находится в той же папке, что и импортирующая его программа:

import example_module

Если требуется отдельный компонент модуля, например функция или класс, то импорт можно осуществить так:

from example_module import some_function, ExampleClass

Обратите внимание: при втором способе импортированные объекты попадают в пространство имён новой программы. Это означает, что они будут объектами новой программы и в программе не должно быть других объектов с такими же именами.

Рассмотрим написанное выше на примере. Пусть имеется программа module_hello.py, в которой находится функция hello(name), возвращающая строку приветствия пользователя по имени. В самой программе кроме функции присутствует вызов этой функции и печать результата её работы. Импортируем из модуля module_hello.py функцию hello(name) в другую программу program.py и также используем для вывода приветствия пользователя.

Код программы module_hello.py:

def hello(name):
    return f"Привет, {name}!"


print(hello(input("Введите своё имя: ")))

Код программы program.py:

from module_hello import hello

print(hello(input("Добрый день. Введите имя: ")))

При выполнении program.py нас ожидает неожиданное действие. Программа сначала запросит имя пользователя, а затем сделает это ещё раз, но с приветствием из program.py.

Введите своё имя: Андрей
Привет, Андрей!
Добрый день. Введите имя: Андрей
Привет, Андрей!

Наша ошибка заключается в том, что программа module_hello.py выполняется полностью, включая основной код с вызовом функции и выводом результата. Исправим программу module_hello.py, добавив проверку, запущена программа или импортирована как модуль:

def hello(name):
    return f"Привет, {name}!"


if __name__ == "__main__":
    print(hello(input("Введите своё имя: ")))

Теперь при импорте модуля module_hello.py код в теле условного оператора выполняться не будет. А основной код этой программы выполнится только при запуске файла как отдельной программы.
Для большего удобства обычно в теле указанного условного оператора вызывают функцию main(), а основной код программы оформляют уже внутри этой функции.
Тогда наш модуль можно переписать так:

def hello(name):
    return f"Привет, {name}!"


def main():
    print(hello(input("Введите своё имя: ")))


if __name__ == "__main__":
    main()

Обратите внимание: при импорте модуля мы можем с помощью символа * указать, что необходимо импортировать все объекты. Например, так:

from some_module import *

Однако делать так крайне не рекомендуется, потому что все объекты модуля добавляются в пространство имён нашей программы, что может приводить к конфликтам.

Содержание:развернуть

  • Как устроен механизм исключений
  • Как обрабатывать исключения в Python (try except)
  • As — сохраняет ошибку в переменную

  • Finally — выполняется всегда

  • Else — выполняется когда исключение не было вызвано

  • Несколько блоков except

  • Несколько типов исключений в одном блоке except

  • Raise — самостоятельный вызов исключений

  • Как пропустить ошибку

  • Исключения в lambda функциях
  • 20 типов встроенных исключений в Python
  • Как создать свой тип Exception

Программа, написанная на языке Python, останавливается сразу как обнаружит ошибку. Ошибки могут быть (как минимум) двух типов:

  • Синтаксические ошибки — возникают, когда написанное выражение не соответствует правилам языка (например, написана лишняя скобка);
  • Исключения — возникают во время выполнения программы (например, при делении на ноль).

Синтаксические ошибки исправить просто (если вы используете IDE, он их подсветит). А вот с исключениями всё немного сложнее — не всегда при написании программы можно сказать возникнет или нет в данном месте исключение. Чтобы приложение продолжило работу при возникновении проблем, такие ошибки нужно перехватывать и обрабатывать с помощью блока try/except.

Как устроен механизм исключений

В Python есть встроенные исключения, которые появляются после того как приложение находит ошибку. В этом случае текущий процесс временно приостанавливается и передает ошибку на уровень вверх до тех пор, пока она не будет обработано. Если ошибка не будет обработана, программа прекратит свою работу (а в консоли мы увидим Traceback с подробным описанием ошибки).

💁‍♂️ Пример: напишем скрипт, в котором функция ожидает число, а мы передаём сроку (это вызовет исключение «TypeError»):

def b(value):
print("-> b")
print(value + 1) # ошибка тут

def a(value):
print("-> a")
b(value)

a("10")

> -> a
> -> b
> Traceback (most recent call last):
> File "test.py", line 11, in <module>
> a("10")
> File "test.py", line 8, in a
> b(value)
> File "test.py", line 3, in b
> print(value + 1)
> TypeError: can only concatenate str (not "int") to str

В данном примере мы запускаем файл «test.py» (через консоль). Вызывается функция «a«, внутри которой вызывается функция «b«. Все работает хорошо до сточки print(value + 1). Тут интерпретатор понимает, что нельзя конкатенировать строку с числом, останавливает выполнение программы и вызывает исключение «TypeError».

Далее ошибка передается по цепочке в обратном направлении: «b» → «a» → «test.py«. Так как в данном примере мы не позаботились обработать эту ошибку, вся информация по ошибке отобразится в консоли в виде Traceback.

Traceback (трассировка) — это отчёт, содержащий вызовы функций, выполненные в определенный момент. Трассировка помогает узнать, что пошло не так и в каком месте это произошло.

Traceback лучше читать снизу вверх ↑

Пример Traceback в Python

В нашем примере Traceback содержится следующую информацию (читаем снизу вверх):

  1. TypeError — тип ошибки (означает, что операция не может быть выполнена с переменной этого типа);
  2. can only concatenate str (not "int") to str — подробное описание ошибки (конкатенировать можно только строку со строкой);
  3. Стек вызова функций (1-я линия — место, 2-я линия — код). В нашем примере видно, что в файле «test.py» на 11-й линии был вызов функции «a» со строковым аргументом «10». Далее был вызов функции «b». print(value + 1) это последнее, что было выполнено — тут и произошла ошибка.
  4. most recent call last — означает, что самый последний вызов будет отображаться последним в стеке (в нашем примере последним выполнился print(value + 1)).

В Python ошибку можно перехватить, обработать, и продолжить выполнение программы — для этого используется конструкция try ... except ....

Как обрабатывать исключения в Python (try except)

В Python исключения обрабатываются с помощью блоков try/except. Для этого операция, которая может вызвать исключение, помещается внутрь блока try. А код, который должен быть выполнен при возникновении ошибки, находится внутри except.

Например, вот как можно обработать ошибку деления на ноль:

try:
a = 7 / 0
except:
print('Ошибка! Деление на 0')

Здесь в блоке try находится код a = 7 / 0 — при попытке его выполнить возникнет исключение и выполнится код в блоке except (то есть будет выведено сообщение «Ошибка! Деление на 0»). После этого программа продолжит свое выполнение.

💭 PEP 8 рекомендует, по возможности, указывать конкретный тип исключения после ключевого слова except (чтобы перехватывать и обрабатывать конкретные исключения):

try:
a = 7 / 0
except ZeroDivisionError:
print('Ошибка! Деление на 0')

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

try:
a = 7 / 0
except Exception:
print('Любая ошибка!')

As — сохраняет ошибку в переменную

Перехваченная ошибка представляет собой объект класса, унаследованного от «BaseException». С помощью ключевого слова as можно записать этот объект в переменную, чтобы обратиться к нему внутри блока except:

try:
file = open('ok123.txt', 'r')
except FileNotFoundError as e:
print(e)

> [Errno 2] No such file or directory: 'ok123.txt'

В примере выше мы обращаемся к объекту класса «FileNotFoundError» (при выводе на экран через print отобразится строка с полным описанием ошибки).

У каждого объекта есть поля, к которым можно обращаться (например если нужно логировать ошибку в собственном формате):

import datetime

now = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S")

try:
file = open('ok123.txt', 'r')
except FileNotFoundError as e:
print(f"{now} [FileNotFoundError]: {e.strerror}, filename: {e.filename}")

> 20-11-2021 18:42:01 [FileNotFoundError]: No such file or directory, filename: ok123.txt

Finally — выполняется всегда

При обработке исключений можно после блока try использовать блок finally. Он похож на блок except, но команды, написанные внутри него, выполняются обязательно. Если в блоке try не возникнет исключения, то блок finally выполнится так же, как и при наличии ошибки, и программа возобновит свою работу.

Обычно try/except используется для перехвата исключений и восстановления нормальной работы приложения, а try/finally для того, чтобы гарантировать выполнение определенных действий (например, для закрытия внешних ресурсов, таких как ранее открытые файлы).

В следующем примере откроем файл и обратимся к несуществующей строке:

file = open('ok.txt', 'r')

try:
lines = file.readlines()
print(lines[5])
finally:
file.close()
if file.closed:
print("файл закрыт!")

> файл закрыт!
> Traceback (most recent call last):
> File "test.py", line 5, in <module>
> print(lines[5])
> IndexError: list index out of range

Даже после исключения «IndexError», сработал код в секции finally, который закрыл файл.

p.s. данный пример создан для демонстрации, в реальном проекте для работы с файлами лучше использовать менеджер контекста with.

Также можно использовать одновременно три блока try/except/finally. В этом случае:

  • в try — код, который может вызвать исключения;
  • в except — код, который должен выполниться при возникновении исключения;
  • в finally — код, который должен выполниться в любом случае.

def sum(a, b):
res = 0

try:
res = a + b
except TypeError:
res = int(a) + int(b)
finally:
print(f"a = {a}, b = {b}, res = {res}")

sum(1, "2")

> a = 1, b = 2, res = 3

Else — выполняется когда исключение не было вызвано

Иногда нужно выполнить определенные действия, когда код внутри блока try не вызвал исключения. Для этого используется блок else.

Допустим нужно вывести результат деления двух чисел и обработать исключения в случае попытки деления на ноль:

b = int(input('b = '))
c = int(input('c = '))
try:
a = b / c
except ZeroDivisionError:
print('Ошибка! Деление на 0')
else:
print(f"a = {a}")

> b = 10
> c = 1
> a = 10.0

В этом случае, если пользователь присвоит переменной «с» ноль, то появится исключение и будет выведено сообщение «‘Ошибка! Деление на 0′», а код внутри блока else выполняться не будет. Если ошибки не будет, то на экране появятся результаты деления.

Несколько блоков except

В программе может возникнуть несколько исключений, например:

  1. Ошибка преобразования введенных значений к типу float («ValueError»);
  2. Деление на ноль («ZeroDivisionError»).

В Python, чтобы по-разному обрабатывать разные типы ошибок, создают несколько блоков except:

try:
b = float(input('b = '))
c = float(input('c = '))
a = b / c
except ZeroDivisionError:
print('Ошибка! Деление на 0')
except ValueError:
print('Число введено неверно')
else:
print(f"a = {a}")

> b = 10
> c = 0
> Ошибка! Деление на 0

> b = 10
> c = питон
> Число введено неверно

Теперь для разных типов ошибок есть свой обработчик.

Несколько типов исключений в одном блоке except

Можно также обрабатывать в одном блоке except сразу несколько исключений. Для этого они записываются в круглых скобках, через запятую сразу после ключевого слова except. Чтобы обработать сообщения «ZeroDivisionError» и «ValueError» в одном блоке записываем их следующим образом:

try:
b = float(input('b = '))
c = float(input('c = '))
a = b / c
except (ZeroDivisionError, ValueError) as er:
print(er)
else:
print('a = ', a)

При этом переменной er присваивается объект того исключения, которое было вызвано. В результате на экран выводятся сведения о конкретной ошибке.

Raise — самостоятельный вызов исключений

Исключения можно генерировать самостоятельно — для этого нужно запустить оператор raise.

min = 100
if min > 10:
raise Exception('min must be less than 10')

> Traceback (most recent call last):
> File "test.py", line 3, in <module>
> raise Exception('min value must be less than 10')
> Exception: min must be less than 10

Перехватываются такие сообщения точно так же, как и остальные:

min = 100

try:
if min > 10:
raise Exception('min must be less than 10')
except Exception:
print('Моя ошибка')

> Моя ошибка

Кроме того, ошибку можно обработать в блоке except и пробросить дальше (вверх по стеку) с помощью raise:

min = 100

try:
if min > 10:
raise Exception('min must be less than 10')
except Exception:
print('Моя ошибка')
raise

> Моя ошибка
> Traceback (most recent call last):
> File "test.py", line 5, in <module>
> raise Exception('min must be less than 10')
> Exception: min must be less than 10

Как пропустить ошибку

Иногда ошибку обрабатывать не нужно. В этом случае ее можно пропустить с помощью pass:

try:
a = 7 / 0
except ZeroDivisionError:
pass

Исключения в lambda функциях

Обрабатывать исключения внутри lambda функций нельзя (так как lambda записывается в виде одного выражения). В этом случае нужно использовать именованную функцию.

20 типов встроенных исключений в Python

Иерархия классов для встроенных исключений в Python выглядит так:

BaseException
SystemExit
KeyboardInterrupt
GeneratorExit
Exception
ArithmeticError
AssertionError
...
...
...
ValueError
Warning

Все исключения в Python наследуются от базового BaseException:

  • SystemExit — системное исключение, вызываемое функцией sys.exit() во время выхода из приложения;
  • KeyboardInterrupt — возникает при завершении программы пользователем (чаще всего при нажатии клавиш Ctrl+C);
  • GeneratorExit — вызывается методом close объекта generator;
  • Exception — исключения, которые можно и нужно обрабатывать (предыдущие были системными и их трогать не рекомендуется).

От Exception наследуются:

1 StopIteration — вызывается функцией next в том случае если в итераторе закончились элементы;

2 ArithmeticError — ошибки, возникающие при вычислении, бывают следующие типы:

  • FloatingPointError — ошибки при выполнении вычислений с плавающей точкой (встречаются редко);
  • OverflowError — результат вычислений большой для текущего представления (не появляется при операциях с целыми числами, но может появиться в некоторых других случаях);
  • ZeroDivisionError — возникает при попытке деления на ноль.

3 AssertionError — выражение, используемое в функции assert неверно;

4 AttributeError — у объекта отсутствует нужный атрибут;

5 BufferError — операция, для выполнения которой требуется буфер, не выполнена;

6 EOFError — ошибка чтения из файла;

7 ImportError — ошибка импортирования модуля;

8 LookupError — неверный индекс, делится на два типа:

  • IndexError — индекс выходит за пределы диапазона элементов;
  • KeyError — индекс отсутствует (для словарей, множеств и подобных объектов);

9 MemoryError — память переполнена;

10 NameError — отсутствует переменная с данным именем;

11 OSError — исключения, генерируемые операционной системой:

  • ChildProcessError — ошибки, связанные с выполнением дочернего процесса;
  • ConnectionError — исключения связанные с подключениями (BrokenPipeError, ConnectionResetError, ConnectionRefusedError, ConnectionAbortedError);
  • FileExistsError — возникает при попытке создания уже существующего файла или директории;
  • FileNotFoundError — генерируется при попытке обращения к несуществующему файлу;
  • InterruptedError — возникает в том случае если системный вызов был прерван внешним сигналом;
  • IsADirectoryError — программа обращается к файлу, а это директория;
  • NotADirectoryError — приложение обращается к директории, а это файл;
  • PermissionError — прав доступа недостаточно для выполнения операции;
  • ProcessLookupError — процесс, к которому обращается приложение не запущен или отсутствует;
  • TimeoutError — время ожидания истекло;

12 ReferenceError — попытка доступа к объекту с помощью слабой ссылки, когда объект не существует;

13 RuntimeError — генерируется в случае, когда исключение не может быть классифицировано или не подпадает под любую другую категорию;

14 NotImplementedError — абстрактные методы класса нуждаются в переопределении;

15 SyntaxError — ошибка синтаксиса;

16 SystemError — сигнализирует о внутренне ошибке;

17 TypeError — операция не может быть выполнена с переменной этого типа;

18 ValueError — возникает когда в функцию передается объект правильного типа, но имеющий некорректное значение;

19 UnicodeError — исключение связанное с кодирование текста в unicode, бывает трех видов:

  • UnicodeEncodeError — ошибка кодирования;
  • UnicodeDecodeError — ошибка декодирования;
  • UnicodeTranslateError — ошибка перевода unicode.

20 Warning — предупреждение, некритическая ошибка.

💭 Посмотреть всю цепочку наследования конкретного типа исключения можно с помощью модуля inspect:

import inspect

print(inspect.getmro(TimeoutError))

> (<class 'TimeoutError'>, <class 'OSError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)

📄 Подробное описание всех классов встроенных исключений в Python смотрите в официальной документации.

Как создать свой тип Exception

В Python можно создавать свои исключения. При этом есть одно обязательное условие: они должны быть потомками класса Exception:

class MyError(Exception):
def __init__(self, text):
self.txt = text

try:
raise MyError('Моя ошибка')
except MyError as er:
print(er)

> Моя ошибка


С помощью try/except контролируются и обрабатываются ошибки в приложении. Это особенно актуально для критически важных частей программы, где любые «падения» недопустимы (или могут привести к негативным последствиям). Например, если программа работает как «демон», падение приведет к полной остановке её работы. Или, например, при временном сбое соединения с базой данных, программа также прервёт своё выполнение (хотя можно было отловить ошибку и попробовать соединиться в БД заново).

Вместе с try/except можно использовать дополнительные блоки. Если использовать все блоки описанные в статье, то код будет выглядеть так:

try:
# попробуем что-то сделать
except (ZeroDivisionError, ValueError) as e:
# обрабатываем исключения типа ZeroDivisionError или ValueError
except Exception as e:
# исключение не ZeroDivisionError и не ValueError
# поэтому обрабатываем исключение общего типа (унаследованное от Exception)
# сюда не сходят исключения типа GeneratorExit, KeyboardInterrupt, SystemExit
else:
# этот блок выполняется, если нет исключений
# если в этом блоке сделать return, он не будет вызван, пока не выполнился блок finally
finally:
# этот блок выполняется всегда, даже если нет исключений else будет проигнорирован
# если в этом блоке сделать return, то return в блоке

Подробнее о работе с исключениями в Python можно ознакомиться в официальной документации.

Toggle table of contents sidebar

try/except#

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

Как правило, Python довольно понятно реагирует на подобные ошибки, и их
можно исправить.

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

Примеры исключений:

In [1]: 2/0
-----------------------------------------------------
ZeroDivisionError: division by zero

In [2]: 'test' + 2
-----------------------------------------------------
TypeError: must be str, not int

В данном случае возникло два исключения: ZeroDivisionError и
TypeError.

Чаще всего можно предсказать, какого рода исключения возникнут во время
исполнения программы.

Например, если программа на вход ожидает два числа, а на выходе выдает
их сумму, а пользователь ввел вместо одного из чисел строку, появится
ошибка TypeError, как в примере выше.

Python позволяет работать с исключениями. Их можно перехватывать и
выполнять определенные действия в том случае, если возникло исключение.

Примечание

Когда в программе возникает исключение, она сразу завершает работу.

Для работы с исключениями используется конструкция try/except:

In [3]: try:
   ...:     2/0
   ...: except ZeroDivisionError:
   ...:     print("You can't divide by zero")
   ...:
You can't divide by zero

Конструкция try работает таким образом:

  • сначала выполняются выражения, которые записаны в блоке try

  • если при выполнения блока try не возникло никаких исключений, блок except пропускается,
    и выполняется дальнейший код

  • если во время выполнения блока try в каком-то месте возникло исключение,
    оставшаяся часть блока try пропускается

    • если в блоке except указано исключение, которое возникло, выполняется код в блоке except

    • если исключение, которое возникло, не указано в блоке except,
      выполнение программы прерывается и выдается ошибка

Обратите внимание, что строка Cool! в блоке try не выводится:

In [4]: try:
   ...:     print("Let's divide some numbers")
   ...:     2/0
   ...:     print('Cool!')
   ...: except ZeroDivisionError:
   ...:     print("You can't divide by zero")
   ...:
Let's divide some numbers
You can't divide by zero

В конструкции try/except может быть много except, если нужны разные
действия в зависимости от типа ошибки.

Например, скрипт divide.py делит два числа введенных пользователем:

# -*- coding: utf-8 -*-

try:
    a = input("Введите первое число: ")
    b = input("Введите второе число: ")
    print("Результат: ", int(a)/int(b))
except ValueError:
    print("Пожалуйста, вводите только числа")
except ZeroDivisionError:
    print("На ноль делить нельзя")

Примеры выполнения скрипта:

$ python divide.py
Введите первое число: 3
Введите второе число: 1
Результат:  3

$ python divide.py
Введите первое число: 5
Введите второе число: 0
На ноль делить нельзя

$ python divide.py
Введите первое число: qewr
Введите второе число: 3
Пожалуйста, вводите только числа

В данном случае исключение ValueError возникает, когда пользователь
ввел строку вместо числа, во время перевода строки в число.

Исключение ZeroDivisionError возникает в случае, если второе число было
равным 0.

Если нет необходимости выводить различные сообщения на ошибки ValueError
и ZeroDivisionError, можно сделать так (файл divide_ver2.py):

# -*- coding: utf-8 -*-

try:
    a = input("Введите первое число: ")
    b = input("Введите второе число: ")
    print("Результат: ", int(a)/int(b))
except (ValueError, ZeroDivisionError):
    print("Что-то пошло не так...")

Проверка:

$ python divide_ver2.py
Введите первое число: wer
Введите второе число: 4
Что-то пошло не так...

$ python divide_ver2.py
Введите первое число: 5
Введите второе число: 0
Что-то пошло не так...

Примечание

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

Это делать не рекомендуется!

try/except/else#

В конструкции try/except есть опциональный блок else. Он выполняется в
том случае, если не было исключения.

Например, если необходимо выполнять в дальнейшем какие-то операции с
данными, которые ввел пользователь, можно записать их в блоке else (файл
divide_ver3.py):

# -*- coding: utf-8 -*-

try:
    a = input("Введите первое число: ")
    b = input("Введите второе число: ")
    result = int(a)/int(b)
except (ValueError, ZeroDivisionError):
    print("Что-то пошло не так...")
else:
    print("Результат в квадрате: ", result**2)

Пример выполнения:

$ python divide_ver3.py
Введите первое число: 10
Введите второе число: 2
Результат в квадрате:  25

$ python divide_ver3.py
Введите первое число: werq
Введите второе число: 3
Что-то пошло не так...

try/except/finally#

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

Сюда ставятся действия, которые надо выполнить в любом случае. Например,
это может быть закрытие файла.

Файл divide_ver4.py с блоком finally:

# -*- coding: utf-8 -*-

try:
    a = input("Введите первое число: ")
    b = input("Введите второе число: ")
    result = int(a)/int(b)
except (ValueError, ZeroDivisionError):
    print("Что-то пошло не так...")
else:
    print("Результат в квадрате: ", result**2)
finally:
    print("Вот и сказочке конец, а кто слушал - молодец.")

Проверка:

$ python divide_ver4.py
Введите первое число: 10
Введите второе число: 2
Результат в квадрате:  25
Вот и сказочке конец, а кто слушал - молодец.

$ python divide_ver4.py
Введите первое число: qwerewr
Введите второе число: 3
Что-то пошло не так...
Вот и сказочке конец, а кто слушал - молодец.

$ python divide_ver4.py
Введите первое число: 4
Введите второе число: 0
Что-то пошло не так...
Вот и сказочке конец, а кто слушал - молодец.

Когда использовать исключения#

Как правило, один и тот же код можно написать и с использованием
исключений, и без них.

Например, этот вариант кода:

while True:
    a = input("Введите число: ")
    b = input("Введите второе число: ")
    try:
        result = int(a)/int(b)
    except ValueError:
        print("Поддерживаются только числа")
    except ZeroDivisionError:
        print("На ноль делить нельзя")
    else:
        print(result)
        break

Можно переписать таким образом без try/except (файл
try_except_divide.py):

while True:
    a = input("Введите число: ")
    b = input("Введите второе число: ")
    if a.isdigit() and b.isdigit():
        if int(b) == 0:
            print("На ноль делить нельзя")
        else:
            print(int(a)/int(b))
            break
    else:
        print("Поддерживаются только числа")

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

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

Если вы раньше использовали какой-то другой язык программирования, есть
вероятность, что в нём использование исключений считалось плохим тоном.
В Python это не так. Чтобы немного больше разобраться с этим вопросом,
посмотрите ссылки на дополнительные материалы в конце этого раздела.

raise#

Иногда в коде надо сгенерировать исключение, это можно сделать так:

raise ValueError("При выполнении команды возникла ошибка")

Встроенные исключения#

В Python есть много встроенных исключений,
каждое из которых генерируется в
определенной ситуации.

Например, TypeError обычно генерируется когда ожидался один тип данных, а передали другой

In [1]: "a" + 3
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-5aa8a24e3e06> in <module>
----> 1 "a" + 3
TypeError: can only concatenate str (not "int") to str

ValueError когда значение не соответствует ожидаемому:

In [2]: int("a")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-2-d9136db7b558> in <module>
----> 1 int("a")
ValueError: invalid literal for int() with base 10: 'a'

Уровень сложности
Средний

Время на прочтение
8 мин

Количество просмотров 6.6K

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

  • Что такое обработка исключений?

  • Разница между оператором if и обработкой исключений.

  • Использование разделов else и finally блока try-except для организации правильного обращения с ошибками.

  • Определение пользовательских исключений.

  • Рекомендации по обработке исключений.

Что такое обработка исключений?

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

Когда возникают исключения — Python выполняет поиск подходящего обработчика исключений. После этого, если обработчик будет найден, выполняется его код, в котором предпринимаются уместные действия. Это может быть логирование данных, вывод сообщения, попытка восстановить работу программы после возникновения ошибки. В целом можно сказать, что обработка исключения помогает повысить надёжность Python-приложений, улучшает возможности по их поддержке, облегчает их отладку.

Различия между оператором if и обработкой исключений

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

Оператор if — это базовый строительный элемент структурного программирования. Этот оператор проверяет условие и выполняет различные блоки кода, основываясь на том, истинно проверяемое условие или ложно. Вот пример:

temperature = int(input("Please enter temperature in Fahrenheit: "))
if temperature > 100:
    print("Hot weather alert! Temperature exceeded 100°F.")
elif temperature >= 70:
    print("Warm day ahead, enjoy sunny skies.")
else:
    print("Bundle up for chilly temperatures.")

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

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

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

# Определение функции, которая пытается поделить число на ноль
def divide(x, y):
    result = x / y
    return result
# Вызов функции divide с передачей ей x=5 и y=0
result = divide(5, 0)
print(f"Result of dividing {x} by {y}: {result}")

Вывод:

Traceback (most recent call last):
  File "<stdin>", line 8, in <module>
ZeroDivisionError: division by zero attempted

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

Вышеописанное исключение можно обработать, обернув вызов функции divide в блок try-except:

# Определение функции, которая пытается поделить число на ноль
def divide(x, y):
    result = x / y
    return result
# Вызов функции divide с передачей ей x=5 и y=0
try:
    result = divide(5, 0)
    print(f"Result of dividing {x} by {y}: {result}")
except ZeroDivisionError:
    print("Cannot divide by zero.")

Вывод:

Cannot divide by zero.

Сделав это, мы аккуратно обработали исключение ZeroDivisionError, предотвратили аварийное завершение остального кода из-за необработанного исключения.

Подробности о других встроенных Python-исключениях можно найти здесь.

Использование разделов else и finally блока try-except для организации правильного обращения с ошибками

При работе с исключениями в Python рекомендуется включать в состав блоков try-except и раздел else, и раздел finally. Раздел else позволяет программисту настроить действия, производимые в том случае, если при выполнении кода, который защищают от проблем, не было вызвано исключений. А раздел finally позволяет обеспечить обязательное выполнение неких заключительных операций, вроде освобождения ресурсов, независимо от факта возникновения исключений (вот и вот — полезные материалы об этом).

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

Использование разделов else и finally позволяет поступить именно так — обработать данные обычным образом в том случае, если исключений не возникло, либо обработать любые исключения, но, как бы ни развивались события, в итоге закрыть файл. Без этих разделов код страдал бы уязвимостями в виде утечки ресурсов или неполной обработки ошибок. В результате оказывается, что else и finally играют важнейшую роль в создании устойчивых к ошибкам и надёжных программ.

try:
    # Открытие файла в режиме чтения
    file = open("file.txt", "r")
    print("Successful opened the file")
except FileNotFoundError:
    # Обработка ошибки, возникающей в том случае, если файл не найден
    print("File Not Found Error: No such file or directory")
    exit()
except PermissionError:
    # Обработка ошибок, связанных с разрешением на доступ к файлу
    print("Permission Denied Error: Access is denied")
else:
    # Всё хорошо - сделать что-то с данными, прочитанными из файла
    content = file.read().decode('utf-8')
    processed_data = process_content(content)
    
# Прибираемся после себя даже в том случае, если выше возникло исключение
finally:
    file.close()

В этом примере мы сначала пытаемся открыть файл file.txt для чтения (в подобной ситуации можно использовать выражение with, которое гарантирует правильное автоматическое закрытие объекта файла после завершения работы). Если в процессе выполнения операций файлового ввода/вывода возникают ошибки FileNotFoundError или PermissionError — выполняются соответствующие разделы except. Здесь, ради простоты, мы лишь выводим на экран сообщения об ошибках и выходим из программы в том случае, если файл не найден.

В противном случае, если в блоке try исключений не возникло, мы продолжаем работу, обрабатывая содержимое файла в ветви else. И наконец — выполняется «уборка» — файл закрывается независимо от возникновения исключения. Это обеспечивает блок finally (подробности смотрите здесь).

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

Определение пользовательских исключений

В Python можно определять пользовательские исключения путём создания подклассов встроенного класса Exception или любых других классов, являющихся прямыми наследниками Exception.

Для того чтобы определить собственное исключение — нужно создать новый класс, являющийся наследником одного из подходящих классов, и оснастить этот класс атрибутами, соответствующими нуждам программиста. Затем новый класс можно использовать в собственном коде, работая с ним так же, как работают со встроенными классами исключений.

Вот пример определения пользовательского исключения, названного InvalidEmailAddress:

class InvalidEmailAddress(ValueError):
    def __init__(self, message):
        super().__init__(message)
        self.msgfmt = message

Это исключение является наследником ValueError. Его конструктор принимает необязательный аргумент message (по умолчанию он устанавливается в значение invalid email address).

Вызвать это исключение можно в том случае, если в программе встретился адрес электронной почты, имеющий некорректный формат:

def send_email(address):
    if isinstance(address, str) == False:
        raise InvalidEmailAddress("Invalid email address")
# Отправка электронного письма

Теперь, если функции send_email() будет передана строка, содержащая неправильно оформленный адрес, то, вместо сообщения стандартной ошибки TypeError, будет выдано настроенное заранее сообщение об ошибке, которое чётко указывает на возникшую проблему. Например, это может выглядеть так:

>>> send_email(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/path/to/project/main.py", line 8, in send_email
    raise InvalidEmailAddress("Invalid email address")
InvalidEmailAddress: Invalid email address

Рекомендации по обработке исключений

Вот несколько рекомендаций, относящихся к обработке ошибок в Python:

  1. Проектируйте код в расчёте на возможное возникновение ошибок. Заранее планируйте устройство кода с учётом возможных сбоев и проектируйте программы так, чтобы они могли бы достойно обрабатывать эти сбои. Это означает — предугадывать возможные пограничные случаи и реализовывать подходящие обработчики ошибок.

  2. Используйте содержательные сообщения об ошибках. Сделайте так, чтобы программа выводила бы, на экран, или в файл журнала, подробные сообщения об ошибках, которые помогут пользователям понять — что и почему пошло не так. Старайтесь не применять обобщённые сообщения об ошибках, наподобие Error occurred или Something bad happened. Вместо этого подумайте об удобстве пользователя и покажите сообщение, в котором будет дан совет по решению проблемы или будет приведена ссылка на документацию. Постарайтесь соблюсти баланс между выводом подробных сообщений и перегрузкой пользовательского интерфейса избыточными данными.

  3. Минимизируйте побочные эффекты. Постарайтесь свести к минимуму последствия сбойных операций, изолируя проблемные разделы кода посредством конструкции try-finally или try с использованием with. Сделайте так, чтобы после выполнения кода, было ли оно удачным или нет, обязательно выполнялись бы «очистительные» операции.

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

  5. Регулярно выполняйте рефакторинг кода. Выполняйте рефакторинг фрагментов кода, подверженных ошибкам, чтобы улучшить их надёжность и производительность. Постарайтесь, чтобы ваша кодовая база была бы устроена по модульному принципу, чтобы её отдельные части слабо зависели бы друг от друга. Это позволяет независимым частям код самостоятельно эволюционировать, не оказывая негативного воздействия на другие его части.

  6. Логируйте важные события. Следите за интересными событиями своего приложения, записывая сведения о них в файл журнала или выводя в консоль. Это поможет вам выявлять проблемы на ранних стадиях их возникновения, не тратя время на длительный анализ большого количества неструктурированных логов.

Итоги

Написание кода обработки ошибок — это неотъемлемая часть индустрии разработки ПО, и, в частности — разработки на Python. Это позволяет разработчикам создавать более надёжные и стабильные программы. Следуя индустриальным стандартам и рекомендациям по обработке исключений, разработчик может сократить время, необходимое на отладку кода, способен обеспечить написание качественных программ и сделать так, чтобы пользователям было бы приятно работать с этими программами.

О, а приходите к нам работать? 🤗 💰

Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.

Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.

Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.

Присоединяйтесь к нашей команде.

First of all, there are exceptions that you want them to break your code (as when this error happens your code will not function anyways!) and exceptions you want to capture silently/smoothly. Try differentiating them. You may not want to capture all exceptions there are!

Second, instead of capturing everything, you could take the time and go through the logs of your process. Let’s say you are getting a different/third-party exception, for example from a cloud service provider like GCP. In the logs, you could find the exception you are getting. Then, you could do something like this:

from google.api_core.exceptions import ServiceUnavailable, RetryError

for i in range(10):
   try:
      print("do something")

   except ValueError:
      print("I know this might happen for now at times! skipping this and continuing with my loop"

   except ServiceUnavailable:
      print("our connection to a service (e.g. logging) of gcp has failed")
      print("initializing the cloud logger again and try continuing ...") 

   except RetryError:
      print("gcp connection retry failed. breaking the loop. try again later!)
      break

For the rest (errors that might or might not happen), I am leaving room for my code to crash if I get an unexpected exception! This way I could understand what is going on and improve my code by capturing edge cases.

If you want this to never crash for some reason, for example if it is a code embedded in a remote hardware that you cannot easily access, you can add a generic exception catcher at the end:

except Exception as e:
   print(f"something went wrong! - {e}")

You can also take a look at Python 3 exception hierarchy here. The difference between Exception and BaseException is that, Exception will not catch SystemExit, KeyboardInterrupt, or GeneratorExit

В этом руководстве мы расскажем, как обрабатывать исключения в Python с помощью try и except. Рассмотрим общий синтаксис и простые примеры, обсудим, что может пойти не так, и предложим меры по исправлению положения.

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

Для обработки большей части этих ошибок как исключений в Python есть блоки try и except.

Для начала разберем синтаксис операторов try и except в Python. Общий шаблон представлен ниже:

try:
	# В этом блоке могут быть ошибки
    
except <error type>:
	# Сделай это для обработки исключения;
	# выполняется, если блок try выбрасывает ошибку
    
else:
	# Сделай это, если блок try выполняется успешно, без ошибок
   
finally:
	# Этот блок выполняется всегда

Давайте посмотрим, для чего используются разные блоки.

Блок try

Блок try — это блок кода, который вы хотите попробовать выполнить. Однако во время выполнения из-за какого-нибудь исключения могут возникнуть ошибки. Поэтому этот блок может не работать должным образом.

Блок except

Блок except запускается, когда блок try не срабатывает из-за исключения. Инструкции в этом блоке часто дают некоторый контекст того, что пошло не так внутри блока try.

Если собираетесь перехватить ошибку как исключение, в блоке except нужно обязательно указать тип этой ошибки. В приведенном выше сниппете место для указания типа ошибки обозначено плейсхолдером <error type> .

except можно использовать и без указания типа ошибки. Но лучше так не делать. При таком подходе не учитывается, что возникающие ошибки могут быть разных типов. То есть вы будете знать, что что-то пошло не так, но что именно произошло, какая была ошибка — вам будет не известно.

При попытке выполнить код внутри блока try также существует вероятность возникновения нескольких ошибок.

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

В результате вы можете столкнуться с IndexError, KeyError и FileNotFoundError. В таком случае нужно добавить столько блоков except, сколько ошибок ожидается – по одному для каждого типа ошибки.

Блок else

Блок else запускается только в том случае, если блок try выполняется без ошибок. Это может быть полезно, когда нужно выполнить ещё какие-то действия после успешного выполнения блока try. Например, после успешного открытия файла вы можете прочитать его содержимое.

Блок finally

Блок finally выполняется всегда, независимо от того, что происходит в других блоках. Это полезно, когда вы хотите освободить ресурсы после выполнения определенного блока кода.

Примечание: блоки else и finally не являются обязательными. В большинстве случаев вы можете использовать только блок try, чтобы что-то сделать, и перехватывать ошибки как исключения внутри блока except.

[python_ad_block]

Итак, теперь давайте используем полученные знания для обработки исключений в Python. Приступим!

Обработка ZeroDivisionError

Рассмотрим функцию divide(), показанную ниже. Она принимает два аргумента – num и div – и возвращает частное от операции деления num/div.

def divide(num,div):
    return num/div

Вызов функции с разными аргументами возвращает ожидаемый результат:

res = divide(100,8)
print(res)

# Output
# 12.5

res = divide(568,64)
print(res)

# Output
# 8.875

Этот код работает нормально, пока вы не попробуете разделить число на ноль:

divide(27,0)

Вы видите, что программа выдает ошибку ZeroDivisionError:

# Output
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-19-932ea024ce43> in <module>()
----> 1 divide(27,0)

<ipython-input-1-c98670fd7a12> in divide(num, div)
      1 def divide(num,div):
----> 2   return num/div

ZeroDivisionError: division by zero

Можно обработать деление на ноль как исключение, выполнив следующие действия:

  1. В блоке try поместите вызов функции divide(). По сути, вы пытаетесь разделить num на div (try в переводе с английского — «пытаться», — прим. перев.).
  2. В блоке except обработайте случай, когда div равен 0, как исключение.
  3. В результате этих действий при делении на ноль больше не будет выбрасываться ZeroDivisionError. Вместо этого будет выводиться сообщение, информирующее пользователя, что он попытался делить на ноль.

Вот как все это выглядит в коде:

try:
    res = divide(num,div)
    print(res)
except ZeroDivisionError:
    print("You tried to divide by zero :( ")

При корректных входных данных наш код по-прежнему работает великолепно:

divide(10,2)
# Output
# 5.0

Когда же пользователь попытается разделить на ноль, он получит уведомление о возникшем исключении. Таким образом, программа завершается корректно и без ошибок.

divide(10,0)
# Output
# You tried to divide by zero :(

Обработка TypeError

В этом разделе мы разберем, как использовать try и except для обработки TypeError в Python.

Рассмотрим функцию add_10(). Она принимает число в качестве аргумента, прибавляет к нему 10 и возвращает результат этого сложения.

def add_10(num):
    return num + 10

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

result = add_10(89)
print(result)

# Output
# 99

Теперь попробуйте вызвать функцию add_10(), передав ей в качестве аргумента не число, а строку.

add_10 ("five")

Ваша программа вылетит со следующим сообщением об ошибке:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-9844e949c84e> in <module>()
----> 1 add_10("five")

<ipython-input-13-2e506d74d919> in add_10(num)
      1 def add_10(num):
----> 2   return num + 10

TypeError: can only concatenate str (not "int") to str

Сообщение об ошибке TypeError: can only concatenate str (not "int") to str говорит о том, что можно сложить только две строки, а не добавить целое число к строке.

Обработаем TypeError:

  • В блок try мы помещаем вызов функции add_10() с my_num в качестве аргумента. Если аргумент допустимого типа, исключений не возникнет.
  • В противном случае срабатывает блок except, в который мы помещаем вывод уведомления для пользователя о том, что аргумент имеет недопустимый тип.

Это показано ниже:

my_num = "five"
try:
    result = add_10(my_num)
    print(result)
except TypeError:
    print("The argument `num` should be a number")

Поскольку теперь вы обработали TypeError как исключение, при передаче невалидного аргумента ошибка не возникает. Вместо нее выводится сообщение, что аргумент имеет недопустимый тип.

The argument `num` should be a number

Обработка IndexError

Если вам приходилось работать со списками или любыми другими итерируемыми объектами, вы, вероятно, сталкивались с IndexError.

Это связано с тем, что часто бывает сложно отслеживать все изменения в итерациях. И вы можете попытаться получить доступ к элементу по невалидному индексу.

В этом примере список my_list состоит из 4 элементов. Допустимые индексы — 0, 1, 2 и 3 и -1, -2, -3, -4, если вы используете отрицательную индексацию.

Поскольку 2 является допустимым индексом, вы видите, что элемент с этим индексом (C++) распечатывается:

my_list = ["Python","C","C++","JavaScript"]
print(my_list[2])

# Output
# C++

Но если вы попытаетесь получить доступ к элементу по индексу, выходящему за пределы допустимого диапазона, вы столкнетесь с IndexError:

print(my_list[4])
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-7-437bc6501dea> in <module>()
      1 my_list = ["Python","C","C++","JavaScript"]
----> 2 print(my_list[4])

IndexError: list index out of range

Теперь вы уже знакомы с шаблоном, и вам не составит труда использовать try и except для обработки данной ошибки.

В приведенном ниже фрагменте кода мы пытаемся получить доступ к элементу по индексу search_idx.

search_idx = 3
try:
    print(my_list[search_idx])
except IndexError:
    print("Sorry, the list index is out of range")

Здесь search_idx = 3 является допустимым индексом, поэтому в результате выводится соответствующий элемент — JavaScript.

Если search_idx находится за пределами допустимого диапазона индексов, блок except перехватывает IndexError как исключение, и больше нет длинных сообщений об ошибках.

search_idx = 4
try:
    print(my_list[search_idx])
except IndexError:
    print("Sorry, the list index is out of range")

Вместо этого отображается сообщение о том, что search_idx находится вне допустимого диапазона индексов:

Sorry, the list index is out of range

Обработка KeyError

Вероятно, вы уже сталкивались с KeyError при работе со словарями в Python.

Рассмотрим следующий пример, где у нас есть словарь my_dict.

my_dict ={"key1":"value1","key2":"value2","key3":"value3"}
search_key = "non-existent key"
print(my_dict[search_key])

В словаре my_dict есть 3 пары «ключ-значение»: key1:value1, key2:value2 и key3:value3.

Теперь попытаемся получить доступ к значению, соответствующему несуществующему ключу non-existent key.

Как и ожидалось, мы получим KeyError:

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-2-2a61d404be04> in <module>()
      1 my_dict ={"key1":"value1","key2":"value2","key3":"value3"}
      2 search_key = "non-existent key"
----> 3 my_dict[search_key]

KeyError: 'non-existent key'

Вы можете обработать KeyError почти так же, как и IndexError.

  • Пробуем получить доступ к значению, которое соответствует ключу, определенному search_key.
  • Если search_key — валидный ключ, мы распечатываем соответствующее значение.
  • Если ключ невалиден и возникает исключение — задействуется блок except, чтобы сообщить об этом пользователю.

Все это можно видеть в следующем коде:

try:
    print(my_dict[search_key])
except KeyError:
    print("Sorry, that's not a valid key!")

# Output:
# Sorry, that's not a valid key!

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

Вы можете сделать это, перехватив невалидный ключ как <error_msg> и используя его в сообщении, которое печатается при возникновении исключения:

try:
    print(my_dict[search_key])
except KeyError as error_msg:
    print(f"Sorry,{error_msg} is not a valid key!")

Обратите внимание, что теперь в сообщении об ошибки указано также и имя несуществующего ключа:

Sorry, 'non-existent key' is not a valid key!

Обработка FileNotFoundError

При работе с файлами в Python часто возникает ошибка FileNotFoundError.

В следующем примере мы попытаемся открыть файл my_file.txt, указав его путь в функции open(). Мы хотим прочитать файл и вывести его содержимое.

Однако мы еще не создали этот файл в указанном месте.

my_file = open("/content/sample_data/my_file.txt")
contents = my_file.read()
print(contents)

Поэтому, попытавшись запустить приведенный выше фрагмент кода, мы получим FileNotFoundError:

---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
<ipython-input-4-4873cac1b11a> in <module>()
----> 1 my_file = open("my_file.txt")

FileNotFoundError: [Errno 2] No such file or directory: 'my_file.txt'

А с помощью try и except мы можем сделать следующее:

  • Попробуем открыть файл в блоке try.
  • Обработаем FileNotFoundError в блоке except, сообщив пользователю, что он попытался открыть несуществующий файл.
  • Если блок try завершается успешно и файл действительно существует, прочтем и распечатаем содержимое.
  • В блоке finally закроем файл, чтобы не терять ресурсы. Файл будет закрыт независимо от того, что происходило на этапах открытия и чтения.
try:
    my_file = open("/content/sample_data/my_file.txt")
except FileNotFoundError:
    print(f"Sorry, the file does not exist")
else:
    contents = my_file.read()
    print(contents)
finally:
    my_file.close()

Обратите внимание: мы обработали ошибку как исключение, и программа завершает работу, отображая следующее сообщение:

Sorry, the file does not exist

Теперь рассмотрим случай, когда срабатывает блок else. Файл my_file.txt теперь присутствует по указанному ранее пути.

Вот содержимое этого файла:

Теперь повторный запуск нашего кода работает должным образом.

На этот раз файл my_file.txt присутствует, поэтому запускается блок else и содержимое распечатывается, как показано ниже:

Надеемся, теперь вы поняли, как обрабатывать исключения при работе с файлами.

Заключение

В этом руководстве мы рассмотрели, как обрабатывать исключения в Python с помощью try и except.

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

Надеемся, вам понравился этот урок. Успехов в написании кода!

Перевод статьи «Python Try and Except Statements – How to Handle Exceptions in Python».

На чтение 13 мин Просмотров 4.2к. Опубликовано 12.07.2021

Содержание

  1. Введение в тему
  2. Что такое исключения
  3. Перехват исключений
  4. Несколько блоков except
  5. Вложенные блоки и else
  6. Finally
  7. Управление исключениями
  8. Пользовательские исключения
  9. Запись в лог
  10. Иерархия исключений

Введение в тему

Зачастую возникают ситуации, когда программа или скрипт работают не так, как задумывал программист. Чаще всего это бывает из-за ввода неожиданных данных. Для обработки таких ситуаций в языке программирования Python есть конструкция try except else finally. Это называется обработкой исключений и позволяет контролировать аварийные случаи. Об этом мощном инструменте мы и поговорим в данном уроке.

Что такое исключения

Работа программиста во многом связана с возникающими в коде ошибками. Их приходится находить и исправлять. Особенно опасны так называемые гейзенбаги – ошибки, которые сложно воспроизвести. Так же существуют скрытые ошибки, их ещё можно назвать логическими. Ещё есть ошибки, которые и вовсе не зависят от программы. Представьте, у Вас есть программа-скрапер, которая автоматически скачивает картинки из соцсети. Заходит она на очередную страницу… А сервер сети поломался. Программа выдаст ошибку.

Если говорить именно о Питоне, то сложность ещё и в том, что это не компилируемый, а интерпретируемый язык, то есть код выполняется «на лету», строка за строкой. Это означает, что у Пайтон-программиста нет возможности отловить ошибки на этапе компиляции. Ещё одна сложность заключается в том, что Python – язык со строгой, но динамической типизацией. Частично это решается в последних версиях языка средством под названием «аннотирование типов», но полностью проблемы не устраняет.

И так, существуют следующие виды ошибок:

  • Синтаксические – когда программист нарушает правила самого языка, к примеру, допускает опечатку в ключевом слове;
  • Логические – когда в коде используется не верная логика;
  • Ввода – когда программист предполагал от пользователя ввода одних данных, а введены другие. К примеру, создатель сайта задумывал, что число в форме будет указано с использованием точки в качестве разделителя, а пользователь ввёл «3,14». Именно этот вид ошибок – излюбленная лазейка хакеров.

Синтаксические ошибки – самые простые, поскольку интерпретатор сам сообщит Вам о них при попытке запустить скрипт.

Простой пример, напечатали команду print с большой буквы:


Print('Hello World!')
# Вывод

Traceback (most recent call last):

File "C:/Users/ivand/PycharmProjects/pythonProject/main.py", line 1, in <module>

Print('Hello World!')

NameError: name 'Print' is not defined

 

Process finished with exit code 1

Логические ошибки – самые сложные в обработке. Сложность в том, что скрипт запускается и не выдаёт никаких исключений, но результат работы отличается от ожидаемого. В чём причина и где её искать? Понятно, что использован не правильный алгоритм. В таких ситуациях можно посоветовать разбить алгоритм на части и проверять значение переменных в контрольных точках. Вот пример такой ошибки:


from random import randint

random_list = 5
sorted_list = []
for i in range(random_list):
sorted_list.append(randint(1, 99))
print(sorted_list)

for i in range(random_list - 1):
for j in range(random_list - i - 1):
if sorted_list[j] > sorted_list[j + 1]:
sorted_list[j] = sorted_list[j + 1]

print(sorted_list)
# Вывод:

 

[95, 57, 16, 29, 82]

[16, 16, 16, 29, 82]

В этом примере программист хотел сделать сортировку пузырьком, но допустил ошибку. А Вы сможете её найти?

Ошибки ввода, как уже говорилось, это ошибки, чаще всего возникающие из-за того, что программист и пользователь не поняли друг друга. Вот код примера, приведённого выше:


x_var = input('Введите число и мы его разделим на 10 n')
print('Результат деления:', float(x_var) / 10)
# Вывод:

 

Введите число и мы его разделим на 10

3,14

Traceback (most recent call last):

File "C:/Users/ivand/PycharmProjects/pythonProject/main.py", line 2, in <module>

print('Результат деления:', float(x_var) / 10)

ValueError: could not convert string to float: '3,14'

Как вы видите, интерпретатор «выбрасывает» исключение «ValueError» — ошибка значения и останавливает выполнение кода.

Перехват исключений

Если Вам не подходит стандартное поведение языка при возникновении исключений – остановка выполнения, Вы можете перехватить исключение и обработать его. Для таких ситуаций и существует конструкция try except. Данный механизм Python позволяет контролировать непредвиденные ситуации и действовать исходя из новых условий. Проиллюстрируем это используя предыдущий пример:


x_var = input('Введите число и мы его разделим на 10 n')
try:
print('Результат деления:', float(x_var) / 10)
except ValueError:
print('Вы ввели число с запятой, а надо с точкой')
print('Программа завершена')
# Вывод:

Введите число и мы его разделим на 10

3,14

Вы ввели число с запятой, а надо с точкой

Программа завершена

Как Вы можете заметить, программа выполнена полностью. Об этом свидетельствует последняя строка вывода. В блок try необходимо заключить тот участок кода, в котором может возникнуть исключение, а в блоке except – его обработку. Обратите внимание, что в блоке except можно не указывать вид ошибки и тогда будет обработано любое возникшее  в блоке try исключение.

Несколько блоков except

Можно использовать несколько блоков except и обрабатывать в каждом блоке отдельный вид ошибки. Немного перепишем программу из предыдущего примера:


x_var = input('Введите число и мы разделим на него 10 n')
try:
print('Результат деления:', 10 / float(x_var))
except ValueError:
print('Вы ввели число с запятой, а надо с точкой')
except ZeroDivisionError:
print('Вы ввели ноль, но на него делить нельзя')
print('Программа завершена')
# Вывод:

Введите число и мы разделим на него 10

0

Вы ввели ноль, но на него делить нельзя

Программа завершена

Хорошей практикой является написание сперва блоков для конкретных ошибок, а затем для общих случаев, поскольку всех ситуаций не предусмотреть:


x_var = input('Введите число и мы разделим на него 10 n')
try:
Print('Результат деления:', 10 / float(x_var))
except ValueError:
print('Вы ввели число с запятой, а надо с точкой')
except ZeroDivisionError:
print('Вы ввели ноль, но на него делить нельзя')
except:
print('Не знаю что, но что-то точно пошло не так')
print('Программа завершена')
# Вывод:

Введите число и мы разделим на него 10

10

Не знаю что, но что-то точно пошло не так

Программа завершена

Вложенные блоки и else

Блоки try-except можно вкладывать друг в друга, если в этом есть необходимость.

Здесь же мы используем блок else. Этот блок должен содержать код, который выполнится если не возникнет исключений.


x_var = input('Введите число и мы разделим на него 10 n')
try:
result = 10 / float(x_var)
try:
print('Результат деления:', result)
except:
print('Не знаю что, но что-то точно пошло не так')
else:
print('Полёт нормальный')
except ValueError:
print('Вы ввели число с запятой, а надо с точкой')
except ZeroDivisionError:
print('Вы ввели ноль, но на него делить нельзя')
else:
print('Программа выполнена без ошибок')
print('Программа завершена')
# Вывод:

Введите число и мы разделим на него 10

10

Результат деления: 1.0

Полёт нормальный

Программа выполнена без ошибок

Программа завершена

Кстати, здесь допущена логическая ошибка. Найдёте?

Finally

Встречаются ситуации, когда необходимо выполнить какую-то часть кода в независимости от того, было исключение или нет. Для этого существует блок finally:


try:
result = 10 / float(x_var)
try:
Print('Результат деления:', result)
except:
print('Не знаю что, но что-то точно пошло не так')
else:
print('Полёт нормальный')
except ValueError:
print('Вы ввели число с запятой, а надо с точкой')
except ZeroDivisionError:
print('Вы ввели ноль, но на него делить нельзя')
finally:
print('Программа завершена')
# Вывод:

Введите число и мы разделим на него 10

10

Не знаю что, но что-то точно пошло не так

Программа завершена

Управление исключениями

В Пайтоне есть возможность создавать свои виды исключений. Ниже мы рассмотрим как это делать, а ещё такую важную вещь как логгирование.

Пользовательские исключения

В Python есть ключевое слово raise. Нужно оно для того чтоб самостоятельно вызывать исключения:


raise Exception("Моя ошибка")
# Вывод:

Traceback (most recent call last):

File "C:/Users/ivand/PycharmProjects/pythonProject/main.py", line 1, in <module>

raise Exception("Моя ошибка")

Exception: Моя ошибка

Такие ошибки тоже можно ловить в try и обрабатывать в except:


x_var = float(input('Введите числоn'))
try:
if x_var > 10:
raise Exception()
except:
print('Что-то пошло не так. Возможно, число слишком большое')
# Вывод:

Введите число

11

Что-то пошло не так. Возможно, число слишком большое

Для того чтобы создать свой тип исключения, необходимо объявить новый класс и унаследовать его от базового типа Exception. Текст ошибки можно передавать используя дандер метод __str__:


class MyException(Exception):
def __str__(self):
return 'Число слишком большое'

x_var = float(input('Введите числоn'))
try:
if x_var > 10:
raise MyException()
except MyException:
print(MyException())
# Вывод:

Введите число

11

Число слишком большое

Так же, текст ошибки можно передавать переопределяя родительский атрибут message:


class MyException(Exception):
def __init__(self):
self.message = 'Число слишком большое'
super().__init__(self.message)

x_var = float(input('Введите числоn'))
try:
if x_var > 10:
raise MyException()
except MyException:
print(MyException())
# Вывод:

Введите число

11

Число слишком большое

Раз мы объявили метод __init__, следует сказать, что в него можно передавать аргументы:


class MyException(Exception):
def __init__(self, x):
self.x = x
self.message = 'Число {} слишком большое'.format(self.x)
super().__init__(self.message)

x_var = float(input('Введите числоn'))
if x_var > 10:
raise MyException(x_var)
# Вывод:

Введите число

11

Traceback (most recent call last):

File "C:/Users/ivand/PycharmProjects/pythonProject/main.py", line 9, in <module>

raise MyException(x_var)

__main__.MyException: Число 11.0 слишком большое

Запись в лог

Часто для отладки программ используют логгирование. Это вывод, чаще всего в отдельный файл, каких-то сообщений, содержащих информацию о том, как программа работает. В том числе, писать в лог можно и текст исключений. В Питоне для этого создали специальный модуль и даже включили его в стандартную библиотеку. Сперва его надо импортировать в Ваш код, а затем указать тип лога:


import logging

logging.basicConfig(level=logging.DEBUG)
logging.debug(" Сообщения про отладку")
logging.info(" Информационные сообщения")
logging.warning(" Предупреждения")
logging.error(" Сообщения с ошибками")
logging.critical(" Ну очень важные сообщения")
# Вывод:

DEBUG:root: Сообщения про отладку

INFO:root: Информационные сообщения

WARNING:root: Предупреждения

ERROR:root: Сообщения с ошибками

CRITICAL:root: Ну очень важные сообщения

Параметр level= указывает, сообщения какого уровня заносить в лог. К примеру, если указать ‘level= logging.ERROR’, то логгироваться будут только сообщения уровня error и critical. Объединим логгирование и обработку исключений:


import logging

logging.basicConfig(filename="log.txt", level=logging.WARNING)
try:
print(10 / 0)
except Exception:
logging.error(str(Exception))

Содержимое файла log.txt:

ERROR:root:<class 'Exception'>

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

В Python есть иерархия исключений. Это происходит из-за того, что их классы наследуются друг от друга. Вот полный список:

BaseException — базовое исключение, от которого берут начало все остальные

+SystemExit  — исключение, порождаемое функцией sys.exit при выходе из программы

+KeyboardInterrupt  — порождается при прерывании программы пользователем (обычно сочетанием клавиш Ctrl+C)

+GeneratorExit  — порождается при вызове метода close объекта generator

+Exception – исключения

++StopIteration — порождается встроенной функцией next, если в итераторе больше нет элементов

++StopAsyncIteration используется для остановки асинхронного прохода

++ArithmeticError — арифметическая ошибка

+++FloatingPointError

+++OverflowError

+++ZeroDivisionError

++AssertionError— выражение в функции assert ложно

++AttributeError — объект не имеет данного атрибута (значения или метода)

++BufferError— операция, связанная с буфером, не может быть выполнена

++EOFError— функция наткнулась на конец файла и не смогла прочитать то, что хотела

++ImportError — не удалось импортирование модуля или его атрибута

+++ModuleNotFoundError

++LookupError— некорректный индекс или ключ

+++IndexError

+++KeyError

++MemoryError— недостаточно памяти

++NameError — не найдено переменной с таким именем

+++UnboundLocalError

++OSError — ошибка, связанная с системой

+++BlockingIOError

+++ChildProcessError

+++ConnectionError

++++BrokenPipeError

++++ConnectionAbortedError

++++ConnectionRefusedError

++++ConnectionResetError

+++FileExistsError

+++FileNotFoundError

+++InterruptedError

+++IsADirectoryError

+++NotADirectoryError

+++PermissionError

+++ProcessLookupError

+++TimeoutError

++ReferenceError — попытка доступа к атрибуту со слабой ссылкой

++RuntimeError — возникает, когда исключение не попадает ни под одну из других категорий

+++NotImplementedError

+++RecursionError

++SyntaxError — синтаксическая ошибка

++IndentationError

++TabError

++SystemError — внутренняя ошибка

++TypeError — операция применена к объекту несоответствующего типа

++ValueError — функция получает аргумент правильного типа, но некорректного значения

+++UnicodeError

++++UnicodeDecodeError

++++UnicodeEncodeError

++++UnicodeTranslateError

++Warning — предупреждение

+++DeprecationWarning

+++PendingDeprecationWarning

+++RuntimeWarning

+++SyntaxWarning

+++UserWarning

+++FutureWarning

+++ImportWarning

+++UnicodeWarning

+++BytesWarning

+++ResourceWarning

Исключения#

В Python существует минимум два типа ошибок: синтаксические ошибки и исключения. Синтаксические ошибки чаще возникают у изучающих python программистов из-за нарушения синтаксиса. Но попытка запуска корректной с точки зрения синтаксиса программы может привести к возникновению ошибки. Ошибки, обнаруженные во время исполнения программы, называются исключениями (exception) и не всегда ведут к падению программы: их можно обрабатывать и восстанавливать нормальный поток исполнения программы.

Тем не менее большинство ошибок программой не обрабатывается и их возникновение приводит к выводу сообщения об ошибке. В качестве примера попробуем поделить на ноль.

---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
~AppDataLocalTemp/ipykernel_13252/2354412189.py in <module>
----> 1 1/0

ZeroDivisionError: division by zero

Как видимо, появилось сообщение об ошибке. Последняя строка этого сообщения, говорит, что конкретно произошло.

  • Исключения бывают разных типов и конкретный тип брошенного исключения печатается до двоеточия. В данном случае это ZeroDivisionError.

  • После типа ошибки выводится дополнительная информация, которая может зависеть от типа ошибки и причины её возникновения.

  • Всё, что идёт до последней строки, демонстрирует в каком контексте возникла ошибка в виде трассировочных данных. Более наглядное представление о трассировочной информации даёт пример, когда ошибка возникает внутри нескольких функций.

def f():
    1/0

def g():
    f()

g()
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
~AppDataLocalTemp/ipykernel_13252/4200539898.py in <module>
      5     f()
      6 
----> 7 g()

~AppDataLocalTemp/ipykernel_13252/4200539898.py in g()
      3 
      4 def g():
----> 5     f()
      6 
      7 g()

~AppDataLocalTemp/ipykernel_13252/4200539898.py in f()
      1 def f():
----> 2     1/0
      3 
      4 def g():
      5     f()

ZeroDivisionError: division by zero

В этот раз сообщение об ошибке гораздо длиннее, потому что ошибка возникла не на самом верхнем уровне, а на уровне функции.

Встроенные исключения#

В python есть приличное количество встроенных исключений, которые интерпретатор бросает при обнаружении ошибки во время исполнения. Например, если попытаться обратиться к необъявленной переменной (несуществующему имени), то возникнет ошибка NameError. Обращение к неправильному атрибуту вызовет ошибку AttributeError, обращение за пределы списка — IndexError, поиск в словаре по несуществующему ключу — KeyError и т.д.

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

В python всё является объектом, в том числе и исключения. При этом у каждого исключения есть свой тип, примеры которых уже обсуждалось выше: NameError, AttributeError, StopIteration и т.д. — это всё тип исключения. Обнаружение ошибки приводит к тому, что создаётся экземпляр исключения соответствующего типа и это исключение начинает распространяться. При этом все встроенные типы исключений находятся в иерархии, с которой можно ознакомиться по ссылке.

Так, на самом верху располагается базовый класс BaseException от которого наследуется все остальные типы исключений. На втором этаже располагаются исключения связанные с завершением исполнения программы (например, SystemExit возникает при вызове функции sys.exit(), KeyboardInterrupt возникает, если пользователь нажимает прерывающую комбинацию клавиш во время работы приложения), а так же класс Exception, который и является базовым классом для всех исключений не связанных с завершением программы.

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

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

Многие исключения бросаются интерпретатором python при обнаружении ошибки или в служебных целях. Для этого используется ключевое слово raise справа от которого указывается экземпляр исключения.

raise Exception("Моё первое исключение!")
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
~AppDataLocalTemp/ipykernel_13252/2359356075.py in <module>
----> 1 raise Exception("Моё первое исключение!")

Exception: Моё первое исключение!

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

def f():
    print("До исключения!")
    raise Exception("Исключение!")
    print("После исключения!")

f()
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
~AppDataLocalTemp/ipykernel_13252/2985915162.py in <module>
      4     print("После исключения!")
      5 
----> 6 f()

~AppDataLocalTemp/ipykernel_13252/2985915162.py in f()
      1 def f():
      2     print("До исключения!")
----> 3     raise Exception("Исключение!")
      4     print("После исключения!")
      5 

Exception: Исключение!

Видим, что инструкция print("До исключения!") исполнилась, а инструкция print("После исключения") — нет, т.к. сразу после возбуждения исключения нормальный поток исполнения программы прекратился и исключение начало распространяться. Исключение распространяется наверх по стеку вызовов, пока не встретится блок, обрабатывающий это исключение. Если такой блок так и не встречается, то выводится сообщение об ошибке и программа падает (или может продолжить ожидание инструкций от пользователя в интерактивном режиме, что происходит, например, в jupyter ноутбуках).

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

Обработка ошибок в python осуществляется с помощью инструкции try, которая может встречаться в блоках двух видов:

  • try/except;

  • try/finally;

Блок try/except#

С помощью блока try/except можно обрабатывать конкретные типы исключений. В самой простой своей форме он выглядит примерно следующим образом.

try:
    инструкции
except ТипИсключения:
    инструкции

После ключевого слова try помещаются инструкции, исполнение которых может привести к возникновению исключения. Инструкции в блоке except выполняются только, если а) вовремя выполнения инструкций в блоке try возникла ошибка и б) её тип совпал с указанным типом.

В качестве примера опять поделим на ноль, но в этот раз поместим это деление в блок try/except и обработаем исключение типа ZeroDivisionError.

try:
    1/0
    print("После попытки деления.")
except ZeroDivisionError:
    print("Перехвачена ошибка деления на ноль!")
Перехвачена ошибка деления на ноль!

Видим, что

  1. сообщения об ошибке с типом ошибки и трассировочной информацией не появилось, т.к. ошибка была обработана;

  2. вместо этого выполнилась инструкция print в блоке except ZeroDivisionError;

  3. инструкция print в блоке try после деления на ноль не выполнилась, т.к. ошибка возникла раньше и начала распространяться.

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

Приведем несколько примеров, чтобы продемонстрировать этот механизм.

Следующий пример демонстрирует, что исключение продолжает распространяться, пока не встретит правильный блок except.

try:
    try:
        1/0
    except NameError:
        print("Ошибка перехвачена внутренним блоком try/except.")
except ZeroDivisionError:
    print("Ошибка перехвачена внешним блоком try/except.")

print("Инструкция после обработки исключения.")
Ошибка перехвачена внешним блоком try/except.
Инструкция после обработки исключения.

Здесь исключение ZeroDivisionError возникло во внутреннем блоке try/except, но тип возникшего исключения не совпал с NameError. В итоге внутренний блок try/except ошибку не обработало и она продолжила распространяться, в результате чего попала во внешний блок try/except, который обрабатывает ошибки типа ZeroDivisionError, что привело прекращению распространения ошибки и к исполнению инструкции print во внешнем блоке. Далее программа продолжает исполняться в нормальном режиме.

Следующий пример демонстрирует, что исключение прекращает распространяться, как только встретит правильный блок except.

try:
    try:
        1/0
    except ZeroDivisionError:
        print("Ошибка перехвачена внутренним блоком except.")
except ZeroDivisionError:
    print("Ошибка перехвачена внешним блоком except.")

print("Инструкция после обработки исключения.")
Ошибка перехвачена внутренним блоком except.
Инструкция после обработки исключения.

Здесь внутренний блок try/except обрабатывает правильный тип исключения. Возникшее исключение прекращает распространяться в этом внутреннем блоке try/except и выполняется инструкция print во внутреннем блоке except. Т.к. исполнение инструкций после внешнего ключевого слова try проходит без ошибок (ошибка возникает, но внешний блок её не замечает, т.к. она обрабатывается внутренним блоком try/except), то внешний блок except игнорируется.

Блоков except может быть несколько в одном блоке try/except, что позволяет по-разному реагировать на ошибки разных типов.

try:
    1/0
except NameError:
    print("Перехвачена ошибка NameError")
except ZeroDivisionError:
    print("Перехвачена ошибка ZeroDivisionError")
Перехвачена ошибка ZeroDivisionError

Интерпретатор читает блоки except сверху вниз и останавливается на первом, который обрабатывает исключение правильного типа. Т.к. типы исключений располагаются в иерархии между собой, то необходимо придерживаться следующего правила.

Tip

Сначала всегда обрабатывайте самые специфичные исключения, а уже потом более общие.

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

try:
    1/0
except Exception:
    print("Перехвачено неожиданное исключение!")
except ZeroDivisionError:
    print("Перехвачено исключение ZeroDivisionError!")
Перехвачено неожиданное исключение!

Здесь в блоке try возникает исключение ZeroDivisionError и начинает распространяться. В блоке except Exception проверка на тип успешно проходит (иерархия между ними выглядит так: Exception (to) ArithmeticError (to) ZeroDivisionError), а значит вызывается инструкции в соответствующем блоке, а до блока except ZeroDivisionError дело так и не доходит: ошибка обработана ранее.

Правильнее в данном случае было бы поменять эти блоки местами.

try:
    1/0
except ZeroDivisionError:
    print("Перехвачено исключение ZeroDivisionError!")
except Exception:
    print("Перехвачено неожиданное исключение!")
Перехвачено исключение ZeroDivisionError!

Дополнительные возможности except#

После except можно указать целевую переменную, чтобы получить доступ к объекту исключения синтаксисом

except ТипИсключения as цель:
    операции над цель

Из некоторых объектов исключений можно получить дополнительную информацию об контексте, при которым они возбудились. У всех стандартных исключений преобразование к строке определенно таким образом, чтобы выводить сообщение, указанное при создании.

try:
    raise Exception("Сообщение исключения!")
except Exception as e:
    print(e)

После ключевого слова except необязательно должен быть указан только один тип исключения: можно указать кортеж.

try:
    1/0
except (ZeroDivisionError, NameError):
    print("Возникло исключение ZeroDivisionError или NameError!")
Возникло исключение ZeroDivisionError или NameError!

Так же, в самом конце блока try/except можно добавить блок else, инструкции в котором исполняться, только если блок try завершится без ошибок. Чтобы продемонстрировать это, определим функцию divide, которая будет пытаться делить первый аргумент на второй и возвращать результат деления, если деление произошло успешно, и возвращать бесконечность, если второй параметр равен нулю.

def divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("Попытка деления на ноль.")
        return float("inf")
    else:
        print("Деление произошло успешно.")
        return result

Блок else в этой функции исполнится, только если в блоке try деление произойдет успешно, т.е. если не возникнет ошибки деления на ноль.

print(divide(42, 14))
print(divide(42, 0))
Деление произошло успешно.
3.0
Попытка деления на ноль.
inf

Блок try/finally#

Синтаксис блока try/except выглядит следующим образом.

try:
    инструкции
finally:
    инструкции

Инструкции в блоке finally исполнятся в не зависимости от того, возникнет ли ошибка в блоке try или нет. Если ошибка все же возникает, то выполняется код в блоке finally и ошибка продолжает распространяться.

Этот блок часто называют обработчик очистки: часто в нем размещают инструкции, которые выполняют чистку (например, освобождение ресурсов). Это позволяет гарантировать, что инструкции в этом блоке исполнятся в любом случае.

Например, раньше часто встречались конструкции следующего вида.

f = open(some_file, "w")
try:
    do_something_with_file(f)
finally:
    f.close()

Т.е. операции над открытым файлом производились в блоке try/finally, что гарантировало, что файл будет закрыт, даже если возникнет исключение. Сегодня гораздо удобнее использовать контекстные менеджеры with для таких целей.

Блок try/except/finally#

Если встречается блок обработки ошибки, в котором встречаются и except и finally, то его можно представить в виде вложения блока try/except внутрь блока try/finally. Иными словами, следующие две конструкции ведут себя одинаково.

try:
    инструкции
except SomeException:
    инструкции
else:
    инструкции
finally:
    инструкции
try:
    try:
        инструкции
    except SomeException:
        инструкции
    else:
        инструкции
finally:
    инструкции

Для демонстрации работы блока такого вида приведем пример из документации.

def divide(x, y):
    try:
        result = x / y
    except ZeroDivisionError:
        print("Попытка деления на ноль!")
    else:
        print(f"Результат деления {result}")
    finally:
        print("Выполнение блока 'finally'.")

Функция divide пытается поделить x на y.

Если деление происходит успешно (else), то выводится результат.

Результат деления 2.0
Выполнение блока 'finally'.

Если возникает ошибка деления на ноль, то она перехватывается и выводится соответствующее сообщение.

Попытка деления на ноль!
Выполнение блока 'finally'.

Если возникает ошибка другого рода, то её распространение продолжается.

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~AppDataLocalTemp/ipykernel_13252/1759864827.py in <module>
----> 1 divide("2", "1")

~AppDataLocalTemp/ipykernel_13252/3246861353.py in divide(x, y)
      1 def divide(x, y):
      2     try:
----> 3         result = x / y
      4     except ZeroDivisionError:
      5         print("division by zero!")

TypeError: unsupported operand type(s) for /: 'str' and 'str'

Блок finally выполняется в любом случае.

Понравилась статья? Поделить с друзьями:
  • Tur hinten re offen перевод ошибка фольксваген
  • Tuple object is not callable python ошибка
  • Tuple object has no attribute append ошибка
  • Tunngle ошибка install incomplete please download and run
  • Tunnelbear ошибка подключения к серверу