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

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

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

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

>>> 1a = 10
File "<stdin>", line 1
1a = 10
^
SyntaxError: invalid syntax

В терминологии языка Python здесь возникло исключение, принадлежащее классу SyntaxError. Согласно документации Python синтаксические ошибки все-таки принято относить к ошибкам, а все остальные – к исключениям. В некоторых языках программирования не используется слово "исключение", а ошибки делят на синтаксические и семантические. Нарушение семантики обычно означает, что, хотя выражения написаны верно с точки зрения синтаксиса языка, программа не работает так, как от нее ожидалось. Для сравнения. Вы можете грамотным русским языком сказать несколько предложений, но по смыслу это будет белиберда, или вас поймут не так, как хотелось бы.

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

Если вы попытаетесь обратиться к переменной, которой не было присвоено значение, что в случае Python означает, что переменная вообще не была объявлена, она не существует, то возникнет исключение NameError.

>>> a = 0
>>> print(a + b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined

Последнюю строку сообщения можно перевести как "Ошибка имени: имя 'b' не определено".

Если исключение возникает при выполнении кода из файла, то вместо "line 1" будет указана строка, в которой оно возникло, например, "line 24". Вместо "<stdin>" будет указано имя файла, например, "test.py". В данном же случае stdin обозначает стандартный поток ввода. По-умолчанию это поток ввода с клавиатуры. Строка 1 – потому что в интерактивном режиме каждое выражение интерпретируется отдельно, как обособленная программка. Если написать выражение, состоящее из нескольких строк, то линия возникновения ошибки может быть другой:

>>> a = 0
>>> if a == 0:
...    print(a)
...    print(a + b)
...
0
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
NameError: name 'b' is not defined

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

>>> int("Hi")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() 
with base 10: 'Hi'
>>> 8 + "3"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) 
for +: 'int' and 'str'

В примере строку "Hi" нельзя преобразовать к целому числу. Возникает исключение ValueError, потому что функция int() не может преобразовать такое значение.

Число 8 и строка "3" принадлежат разным типам, операнд сложения между которыми не поддерживается. При попытке их сложить возникает исключение TypeError.

Деление на ноль вызывает исключение ZeroDivisionError:

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

Обработка исключений. Оператор try-except

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

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

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

n = input("Введите целое число: ")
try:
    n = int(n)
    print("Удачно")
except:
    print("Что-то пошло не так")

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

Если в теле try исключения не возникает, то тело ветки except не выполняется.

Вот пример вывода программы, когда пользователь вводит целое число:

Введите целое число: 100
Удачно

А здесь – когда вводит не то, что ожидалось:

Введите целое число: AA
Что-то пошло не так

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

try:
    n = input('Введите целое число: ')
    n = int(n)
    print("Все нормально. Вы ввели число", n)
except ValueError:
    print("Вы ввели не целое число")

Теперь если сработает тело except мы точно знаем, из-за чего возникла ошибка. Но если в теле try возникнет еще какое-нибудь исключение, то оно не будет обработано. Для него надо написать отдельную ветку except. Рассмотрим программу:

try:
    a = float(input("Введите делимое: "))
    b = float(input("Введите делитель: "))
    c = a / b
    print("Частное: %.2f" % c)
except ValueError:
    print("Нельзя вводить строки")
except ZeroDivisionError:
    print("Нельзя делить на ноль")

При ее выполнении исключения могут возникнуть в трех строчках кода: где происходит преобразование введенных значений к вещественным числам и в месте, где происходит деление. В первом случае может возникнуть ValueError, во втором – ZeroDivisionError. Каждый тип исключения обрабатывается своей веткой except.

Несколько исключений можно сгруппировать в одну ветку и обработать совместно:

try:
    a = float(input("Введите делимое: "))
    b = float(input("Введите делитель: "))
    c = a / b
    print("Частное: %.2f" % c)
except (ValueError, ZeroDivisionError):
    print("Нельзя вводить строки")
    print("или делить на ноль")

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

try:
    n = input('Введите целое число: ')
    n = int(n)
except ValueError:
    print("Вы что-то попутали с вводом")
else: # когда в блоке try не возникло исключения
    print("Все нормально. Вы ввели число", n)
finally: # выполняется в любом случае
    print("Конец программы")

Примечание. В данном коде используются комментарии. В языке Python перед ними ставится знак решетки #. Комментарии в программном коде пишутся исключительно для человека и игнорируются интерпретатором или компилятором.

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

pl@pl-desk:~$ python3 test.py
Введите целое число: 4.3
Вы что-то попутали с вводом
Конец программы
pl@pl-desk:~$ python3 test.py
Введите целое число: 4
Все нормально. Вы ввели число 4
Конец программы

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

Также исключение может возникнуть в блоке except, else или finally, и тогда им нужен собственный обработчик. Модифицируем немного предыдущую программу и специально сгенерируем исключение в теле except:

try:
    n = input('Введите целое число: ')
    n = int(n)
except ValueError:
    print("Вы что-то попутали с вводом")
    3 / 0
except ZeroDivisionError:
    print("Деление на ноль")
else:
    print("Все нормально. Вы ввели число", n)
finally:
    print("Конец программы")

По началу может показаться, что все нормально. Исключение, генерируемое выражением 3 / 0 будет обработано веткой except ZeroDivisionError. Однако это не так. Эта ветка обрабатывает только исключения, возникающие в блоке try, к которому она сама относится. Вот вывод программы, если ввести не целое число:

Введите целое число: а
Вы что-то попутали с вводом
Конец программы
Traceback (most recent call last):
File "test.py", line 15, in <module>
n = int(n)

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

During handling of the above exception,
another exception occurred:

Traceback (most recent call last):
File "test.py", line 18, in <module>
3 / 0
ZeroDivisionError: division by zero

Мало того, что не было обработано деление на ноль, поскольку тело except ValueError неудачно завершилось, само исключение ValueError посчиталось необработанным. Решение проблемы может быть, например, таким:


except ValueError:
    print("Вы что-то попутали с вводом")
    try:
       3 / 0
    except ZeroDivisionError:
        print("Деление на ноль")

Здесь в тело except вложен свой внутренний обработчик исключений.

Практическая работа

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

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

pl@pl-desk:~$ python3 test.py
Первое значение: 4
Второе значение: 5
Результат: 9.0
pl@pl-desk:~$ python3 test.py
Первое значение: a
Второе значение: 9
Результат: a9

Last updated