Web-scraping: сбор данных из баз данных и интернет-источников

Алла Тамбовцева, НИУ ВШЭ

Исключения

Что происходит, когда мы просим Python выполнить недопустимую операцию? Например, возвести строку в квадрат, найти натуральный логарифм от отрицательного числа, вывести на экран элемент списка с несуществующим индексом... Мы получаем сообщение об ошибке (на самом деле, исключение):

In [1]:
x = input("Enter a number: ")  # ввели не число
n = int(x)  # превратить abc в целое число невозможно
Enter a number: abc
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-1-2ec8707d5deb> in <module>
      1 x = input("Enter a number: ")  # ввели не число
----> 2 n = int(x)  # превратить tuy в целое число невозможно

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

В примере выше мы получили исключение типа ValueError, которое свидетельствует о том, что Python не может выполнить запрашиваемое действие с указанным значением. Несмотря на то, что на экран выводится краткое пояснение ошибки, полученное сообщение очень специфическое и вряд ли поможет пользователю. Как быть? Написать программу, которая будет «ловить» ошибки (исключения) и в случае, если ошибка допущена, выводить пользователю более доступное сообщение о том, что именно пошло не так.

Структура конструкций с исключениями напоминает устройство конструкций if-else: есть «развилка», если действие допустимо, оно выполняется (движемся по первой ветке «развилки»), если нет – не выполняется, но ошибки при этом не высвечивается. Конструкция для «ловли» исключений – конструкция try-except. В ветке с try указывается набор действий, которые Python в любом случае пытается выполнить, в except – набор действий, которые должны быть исполнены, если реализовать операции в try не получилось. Обычно при except есть тип исключения, на который программа должна реагировать. В нашем случае это ValueError:

In [2]:
x = input("Enter a number: ")
try:
    n = int(x)
    print("Ok")  # если все хорошо
except ValueError:
    print("Incorrect input. Not a number.") 
Enter a number: ек
Incorrect input. Not a number.

Иногда помимо основных действий добавляют оператор pass, который ничего не делает, а просто означает отсутствие действия. Если в код выше мы допишем pass, ничего не изменится:

In [4]:
x = input("Enter a number: ")
try:
    n = int(x)
    print("Ok")
except ValueError:
    print("Incorrect input. Not a number.")
    pass
Enter a number: rr
Incorrect input. Not a number.

Может возникнуть вопрос, а зачем тогда он вообще нужен? Например, затем, чтобы обозначить отсутствие действия и избежать добавления ненужных строк кода. Если мы хотим, чтобы в случае столкновения с ValueError Python НЕ ДЕЛАЛ НИЧЕГО и не выводил никаких предупреждений на экран, оставив после выражения с except пустоту, мы получим ошибку синтаксиса. А если просто допишем pass, то всё будет, как надо:

In [5]:
x = input("Enter a number: ")
try:
    n = int(x)
    print("Ok")
except ValueError:
    pass # молча
Enter a number: tt

В конструкцию try-except можно включать более одного except, и эти исключения могут быть разными. Рассмотрим пример: пользователь вводит число x, а ему возвращается число 1/x (обратное). Какие проблемы могут возникнуть? Во-первых, пользователь может ввести не число (как в примере выше). Во-вторых, пользователь может ввести 0, а делить на 0, как известно, нельзя.

In [6]:
1 / 0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-6-9e1622b385b6> in <module>
----> 1 1/0

ZeroDivisionError: division by zero

Получили исключение типа ZeroDivisionError. Теперь мы можем расширить нашу конструкцию и по-разному реагировать на разные типы ошибок:

In [7]:
x = input("Enter a number: ")
try:
    n = int(x)
    res = 1 / n
    print("Ok")
    print(res)
    
except ValueError:
    print("Not a valid number.")
    
except ZeroDivisionError:
    print("Cannot divide by zero.")
Enter a number: 0
Cannot divide by zero.

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

In [8]:
x = input("Enter a number: ")
try:
    n = int(x)
    res = 1 / n
    
except:
    print("Something went wrong!")
Enter a number: 0
Something went wrong!