Ошибки и исключения

Материал из Институт биоинформатики
Перейти к: навигация, поиск

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

Типы ошибок

В Python существует два типа ошибок: SyntaxError (Синтаксическая ошибка) и Exception (Исключение). Проще всего рассмотреть их на примерах.

Ссылка на документацию Python о встроенных ошибках и исключениях

SyntaxError

Синтаксическая ошибка (или ошибка грамматического разбора, parsing error) возникает, когда при исполнении код натыкается на отсутствие необходимых синтаксических символов (запятая, двоеточие, отступ) или на лишние символы. Выполнение кода прекращается и появляется сообщение об ошибке. В примере ниже отсутствует двоеточие после объявления функции:

def example(a, b)
    return a + b

Выведет на печать:

  File "<stdin>", line 1
    def example(a, b)
                    ^
SyntaxError: invalid syntax

Обратите внимание, что в тексте ошибки указана строка, в которой встречается ошибка.

Exceptions

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

666/0

Выведет на печать:

Traceback (most recent call last):
  File "<stdin>", line 12, in <module>
    666/0
ZeroDivisionError: division by zero

На этом выполнение кода останавливается и выпадает исключение - ZeroDivisionError. Тип исключения, как правило, достаточно информативен и сразу понятно, что необходимо исправить.

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

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

Например, ZeroDivisionError (деление на ноль) - не единственная ошибка, которая может возникнуть при выполнении арифметических действий. В иерархии рядом с ней можно найти еще OverflowError (возникает, когда результат арифметической операции настолько велик, что Python не может вывести его в консоль) и FloatingPointError (возниакет при некорректном выполнении операций с числом с плавающей точкой. В иерархии эти ошибки логично объединяются в один тип - ArithmeticError, который в свою очередь входят в группу Exception. Когда мы научимся ловить исключения, станет понятно, почему иерархия важна и нужна.

Как ловить исключения?

Учимся ловить исключения

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

x = open('smth.txt', 'r')

Выведет на печать:

Traceback (most recent call last):
  File "<stnin>", line 59, in <module>
    x = open('smth.txt', 'r')
FileNotFoundError: [Errno 2] No such file or directory: 'smth.txt'

Выпадает ошибка FileNotFoundError и выполнение скрипта прекращается, но в Python существует специальная конструкция try-except-else-finally, которая позволяет "поймать" ошибку, если мы знаем, что она может произойти (предполагаем, что файла может и не оказаться) и прописать, что делать, если такая ошибка поймана.

Try-except

Продолжим рассматривать пример с файлом, которого не существует. Для того, чтобы выполнение скрипта не прерывалось, в блоке try пишется команда, которая и может сгенерировать исключение. В блоке except пишется имя ошибки, которую мы собираемся ловить:

try:
    x = open('smth.txt', 'r')
except FileNotFoundError:
    print('No such file detected')

Выведет на печать:

No such file detected

PROFIT! Ошибка поймана, ваш скрипт больше не сломается и не упадет!

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

try:
    x = open('smth.txt', 'r')
except (FileNotFoundError, PermissionError, TimeoutError):
    print('No such file detected')

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

def f(x):
   try:
       return 15 / x
   except TypeError as error:
       print(type(error)) #1
       print(type(error).__name__) #2
       print(error) #3
       print(error.args) #4
f('not a number')

В данном примере мы пытается поделить число на строку, что генерирует TypeError - ошибку типа. Выводит на печать:

<class 'TypeError'> #1 тип данных
TypeError #2 имя исключения
unsupported operand type(s) for /: 'int' and 'str' #3 текст исключения
("unsupported operand type(s) for /: 'int' and 'str'",) #4 аргументы, которые есть у исключения

Причем здесь иерархия?

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

try:
    x = 15/0
except ArithmeticError:
    print('You`re trying to divide number by zero - don`t do that!')

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

try:
    x = 15/0
except:
    print('You`re cought all possible exceptions!')

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

try:
    x = 15/0
except Exception:
    print('You`re cought all possible exceptions!')

Если посмотреть на иерархию, то можно заметить, что так скрипт ловит все исключения, кроме трех:

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

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

  • Еще один остроумный способ сделать так, чтобы скрипт никогда не упал - поставить его целиком под конструкцию try, в таком случае все ошибки тоже будут пойманы, но толку от этого будет немного

Else

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

try:
    x = open('smth.txt', 'r')
except FileNotFoundError:
    print('No such file detected')
else:
    data = x.read()
    print(data)
    x.close

Выводит на печать:

This is a text from existing file

Finally

Finally - еще один блок, который добавляется в try-except-else для того, чтобы выполнить некоторые действия вне зависимости от того, была ли поймана ошибка или нет:

try:
    x = open('smth.txt', 'r')
except FileNotFoundError:
    print('No such file detected')
else:
    data = x.read()
    print(data)
    x.close
finally:
    print('I will print this massage no matter were any exceptions cought')

Выводит на печать:

This is a text from existing file
I will print this massage no matter were any exceptions cought

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

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

  • Функция проверяет, корректно ли введен тип данных. Введем в функцию число, как и требуется:
def type_checker(smth):
    if type(smth) is int:
        print('Input is number, it`s OK')
    if not type(smth) is int:
        raise TypeError('Input should be number, not something else!!!')

type_checker(666)

Выведет в консоль:

Input is number, it`s OK
  • Терерь попытаемся дать функции на вход строку вместо числа:
def type_checker(smth):
    if type(smth) is int:
        print('Input is number, it`s OK')
    if not type(smth) is int:
        raise TypeError('Input should be number, not something else!!!')

type_checker('not_a_number')

Выведет в консоль:

Traceback (most recent call last):
  File "<stdin>", line 126, in <module>
    type_checker('not_a_numner')
  File "<stdin>", line 124, in type_checker
    raise TypeError('Input should be number, not something else!!!')
TypeError: Input should be number, not something else!!!

Текст исключения предупреждает, что на ввод должно подаваться число.

Команду raise можно использовать внутри блока try-except без аргумента:

try:
    15/0
except ZeroDivisionError:
    print('Again you`re trying to divide by zero!')
    raise

Выведет в консоль:

Again you`re trying to divide by zero!
Traceback (most recent call last):
  File "<stdin>", line 131, in <module>
    15/0
ZeroDivisionError: division by zero

Поймали исключение, бросили исключение, пошли писать код нормально!