Ошибки и исключения
Ошибки могут неожиданно появляться в любом программном обеспечении, особенно в крупных проектах. Они могут полностью блокировать работу программы или заставлять ее выполнять не те задачи. Причины таких сбоев разнообразны.
Порой разработчики допускают ошибки в применении синтаксиса языка программирования. Например, использовать в названии переменной первую цифру или забыть двоеточие в структуре сложной инструкции. Такие ошибки именуются синтаксическими, так как они нарушают правила синтаксиса и пунктуации языка. Интерпретатор Python, встретившись с данным недостатком, не может его распознать и прекращает выполнение программы, выводя уведомление об ошибке и указывая на ее место:
>>> 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 24". Вместо "stdin"
будет указано название файла, например, "test.py". В интерактивном режиме stdin
указывает на стандартный ввод, обычно
это клавиатура. Когда каждое выражение рассматривается как самостоятельная программа, места возникновения ошибки могут варьироваться:
>>> 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, предусмотрен оператор, позволяющий перехватывать и обрабатывать исключения, что дает возможность программе продолжить работу или корректно завершить выполнение.
В Python такой оператор называется try-except. "Try" переводится как "попытаться", а "except" — "исключение". Смысл его работы в следующем: "попытаться выполнить некоторое действие, если возникло исключение – предпринять другой шаг". Конструкция оператора try-except напоминает условный оператор с ветвью else. Пример его использования:
n = input("Введите целое число: ") try: n = int(n) print("Удачно") except: print("Что-то пошло не так")
Исключительная ситуация может возникнуть в строке, где n преобразуется в целое число. Если преобразование невозможно, выполнение
оператора try прекращается, и выражение print("Удачно")
не выполнится. Вместо этого программа перейдет к выполнению
блока except.
Если в блоке try исключения не наступают, то ветвь except остаётся незадействованной.
Вот пример, если пользователь корректно вводит целое число:
Введите целое число: 100 Удачно
А здесь – при вводе некорректных данных:
Введите целое число: AA Что-то пошло не так
Однако, есть один недостаток — код выше обработает любое исключение. Но если возникает несколько различных исключений, каждому из них нужен индивидуальный обработчик. Поэтому важно указывать тип исключения после ключевого слова 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("Нельзя вводить строки или делить на ноль")
Помимо except оператор обработки исключений может иметь ветки finally и else, хотя их наличие не обязательно. Блок finally всегда выполняется, независимо от того, была ли вызвана ветка except. Блок else выполняется при отсутствии исключений в блоке try.
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
завершилось ошибкой, из-за чего первоначальная ошибка
также осталась необработанной. Исправить это можно следующим образом:
… except ValueError: print("Вы что-то попутали с вводом") try: 3/ 0 except ZeroDivisionError: print("Деление на ноль") …
Здесь внутрь блока except помещён собственный обработчик исключений.
Практическая работа
Создайте программу, которая запрашивает у пользователя два значения. Если хотя бы одно из них не является числом, выполняйте конкатенацию, то есть соединение строк. В противном случае сложите эти числа.