Исключения в Python. Try/Except/Finally
В этой статье вы узнаете о важном средстве языка, без которого крупная программа не может обойтись. Речь пойдет об исключениях. Что это такое, как ими пользоваться и как создавать собственные?
Исключительные ситуации или исключения (exceptions) – это ошибки, обнаруженные при исполнении. Например, к чему приведет попытка чтения несуществующего файла? Или если файл был случайно удален пока программа работала? Такие ситуации обрабатываются при помощи исключений.
Если же Python не может понять, как обойти сложившуюся ситуацию, то ему не остается ничего кроме как поднять руки и сообщить, что обнаружил ошибку. В общем, исключения необходимы, чтобы сообщать программисту об ошибках.
Простейший пример исключения - деление на ноль:
>>> 100 / 0
Traceback (most recent call last):
File "", line 1, in
100 / 0
ZeroDivisionError: division by zero
В данном случае интерпретатор сообщил нам об исключении ZeroDivisionError – делении на ноль.
Traceback
В большой программе исключения часто возникают внутри. Чтобы упростить программисту понимание ошибки и причины такого поведения Python предлагает Traceback или в сленге – трэйс. Каждое исключение содержит краткую информацию, но при этом полную, информацию о месте появления ошибки. По трэйсу найти и исправить ошибку становится проще.
Рассмотрим такой пример:
Traceback (most recent call last):
File "/home/username/Develop/test/app.py", line 862, in _handle
return route.call(**args)
File "/home/username/Develop/test/app.py", line 1729, in wrapper
rv = callback(*a, **ka)
File "/home/username/Develop/test/__init__.py", line 76, in wrapper
body = callback(*args, **kwargs)
File "/home/username/Develop/test/my_app.py", line 16, in index
raise Exception('test exception')
Рассмотрим какие ещё встречаются комментарии к исключениям:
Traceback (most recent call last): File "", line 1, in 2 + '1' TypeError: unsupported operand type(s) for +: 'int' and 'str' В данном примере при попытке сложить целое число и строку мы получаем исключение TypeError. В описании сразу же становится ясно, что же мы не так написали. Traceback (most recent call last): File "", line 1, in int('qwerty') ValueError: invalid literal for int() with base 10: 'qwerty' Приведение строчки к целому числу приводит к исключению ValueError.В трэйсе этих двух примеров можно прочесть, что в таком-то файле на такой-то строчке есть ошибки.
На этом список встроенных исключений не заканчивается, в следующем разделе рассмотрены основные исключения и причины их возникновения.
Иерархия исключений Исключение, которое вы не увидите при выполнении кода – это BaseException – базовое исключение, от которого берут начало остальные.
В иерархии исключений две основные группы:
Системные исключения и ошибки Обыкновенные исключения Если обработку первых лучше не делать (если и делать, то надо четко понимать для чего), то обработку вторых целиком и полностью Python возлагает на плечи программиста.
К системным можно смело отнести:
SystemExit – исключение, порождаемое функцией sys.exit при выходе из программы. KeyboardInterrupt – возникает при прерывании программы пользователем (обычно сочетанием клавиш Ctrl+C). GeneratorExit — возникает при вызове метода close объекта generator. Остальные исключения – это "обыкновенные". Спектр уже готовых исключений велик.
Для Python2 иерархию исключений можно представить так:
Список исключений покрывает большой объем ситуаций и ошибок программиста. Если предупреждения (warning) только просят обратить внимание, то ошибки уже могут остановить исполнение программы.
В Python3 появились новые исключения и иерархия стала такова:
В целом заметно, что при создании Python3 добавлен блок новых исключений. Но даже этих почти 70 исключений не хватает при написании программ на языке Python.
Использование исключений Мы рассмотрели что такое исключения, какие они бывают и как их анализировать. Но до сих пор явно не рассмотрели такую важную вещь, как их использование.
Начнем с обработки.
Обработка исключений Давайте рассмотрим случай с делением на 0.
Данный код приведет к исключению ZeroDivisionError. Чтобы этого не случилось, воспользуемся конструкцией try..except, например, так:>>> try:
... a = 100
... b = 0
... c = a / b
... except ZeroDivisionError as e:
... print(e)
...
division by zero
Например, мы условились, что значение переменной c в случае ошибки деления равно -1. Тогда модифицируем код:
Перед тем как идти дальше, рассмотрим ещё одну возможность.Пускай у нас файл с данными в файловой системе, и необходимо его прочитать. В этом случае сразу же всплывают несколько исключительных ситуаций, такие как: нет файла, файл битый, файл пустой (по заданию мы знаем, что в нём данные) и другие.
Используя исключения, можно вот так решить эту задачу:
try:
filepath = 'test_file.txt'
with open(filepath, 'r') as fio:
result = fio.readlines()
if not result:
raise Exception("File is empty")
except IOError as e:
result = []
except Exception as e:
result = []
print(e)
Если обработка для разных типов исключений одинакова, то уменьшить количество кода становится не проблемой:
Вызов исключений При работе с исключениями программист тратит большую часть времени на обработку, но при этом возникают ситуации, когда исключениями надо и бросать в других.
На сленге программистов "бросить исключение" означает написать код, который при исполнении будет инициировать исключительную ситуацию.
Например, функция, которая решает квадратное уравнение. Вы условились, что корни только вещественные, тогда в случае комплексных корней стоит бросить исключение.
Чтобы бросить исключение необходимо воспользоваться raise
Пример:
Если при обработке исключения вы желаете пробросить его ещё выше, то следует написать такой код: Собственные исключения При написании собственных программ разумное желание добавить выразительности коду, а так же обратить внимание других программистов на особые исключительные ситуации. Для решения этой задачи стоит использовать собственные исключения.В минимальном исполнении необходимо наследоваться от какого-нибудь класса в иерархии исключений. Например так:
class MyException(Exception):
pass
````
Тогда можно бросить своё исключение:
raise MyException(Exception)
Легко заметить, мы создаем класс, а значит всё, что мы знаем о классах, справедливо и для исключений. Можно завести переменные и делать их обработку.
Как правило, исключения это очень маленькие классы. Они должны выполняться максимально быстро.
Дополнение: Полная форма try..except
Форма try...except не полная, полной же является try..except..else..finally.
Применение полной конструкции может заметно упростить код, а также сделать его более безопасным.
Представим, что в программе происходит чтение файла и необходимо убедиться, что объект файла был корректно закрыт и что не возникло никакого исключения. Этого можно достичь с применением блока finally.
Иными словами, finally выполняет блок инструкций в любом случае, было ли исключение, или нет. А инструкция else выполняется в том случае, если исключения не было.
В целом, использование полной формы таково: