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

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

Порой разработчики допускают ошибки в применении синтаксиса языка программирования. Например, использовать в названии переменной первую цифру или забыть двоеточие в структуре сложной инструкции. Такие ошибки именуются синтаксическими, так как они нарушают правила синтаксиса и пунктуации языка. Интерпретатор 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 помещён собственный обработчик исключений.

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

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

Вопросы для самопроверки:

  1. Что такое синтаксическая ошибка в контексте программирования на Python?
  2. Какое исключение возникает при обращении к несуществующей переменной в Python?
  3. Что делает оператор try-except в языке программирования Python?
  4. Какой оператор используется для выполнения кода независимо от того, было ли исключение или нет?
  5. Как можно обрабатывать несколько разных исключений в одном блоке except?
  6. Почему важно указывать тип исключения после ключевого слова except?

Программа курса:

  1. Описание курса
  2. Эволюция и основы языков программирования

    История программирования в кратком и понятном изложении. Что такое машинный язык, почему появились ассемблеры, языки высокого уровня и объектно-ориентированные. Зачем нужен транслятор, и почему он может быть либо компилятором, либо интерпретатором.

  3. Знакомство с основами Python

    Особенности языка Python, работа в интерактивном режиме и подготовка файлов с исходным кодом.

  4. Типы данных и переменные в Python

    Базовые типы данных в Python: целое, вещественное числа, строки. Изменение типа данных с помощью встроенных функций. Понятие об операциях и переменных. Присваивание значения переменной.

  5. Ввод и вывод данных с Python функциями

    Для вывода на экран в Python 3.x используется функция print(). Вывод может быть предварительно отформатирован. Для ввода данных с клавиатуры используется функция input(), которая возвращает в программу строку.

  6. Логические выражения и операторы в Python

    Логические выражения. Логические операторы языка Python: == (равно), != (не равно), (больше), = (больше или равно), and (логическое И), or (логическое ИЛИ), not (отрицание).

  7. Ветвление и условные операторы в Python

    Управление потоком программы с помощью операторов if-else. Создание логических ветвлений, обработка условий и выполнение разных блоков кода в Python.

  8. Изучите обработку ошибок и исключений в Python

    Общее представление об ошибках и исключениях в языке программирования Python. SyntaxError, NameError, TypeError, ValueError, ZeroDivisionError. Обработка исключений с помощью инструкции try-except.

  9. Множественное ветвление с if-elif-else в Python

    Оператор множественного ветвления языка Python позволяет организовать более двух веток выполнения программы без необходимости вложения условных операторов друг в друга. Конструкция включает одну ветку if, произвольное количество elif и необязательную ветку else.

  10. Цикл while и его применение в Python

    С помощью циклов в программировании организуется многократное следующее друг за другом выполнение одних и тех же участков кода. Бывают циклы с условием и со счетчиком. К первым относится цикл while, или цикл "пока".

  11. Изучите функции и их применение в Python

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

  12. Локальные и глобальные переменные в Python

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

  13. Оператор return и возврат значений в Python

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

  14. Параметры и аргументы функций в Python

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

  15. Встроенные функции Python для работы

    Язык программирования Python включает множество встроенных функций. В предыдущих уроках мы использовали такие функции как print() и input(), а также функции преобразования типов данных. В этом уроке рассматриваются встроенные функции для работы с символами и числами.

  16. Использование модулей в Python

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

  17. Генерация псевдослучайных чисел в Python

    Для генерации псевдослучайных чисел в языке программирования Python используются функции модуля random. Функция random() генерирует вещественное число от 0 до 1. Функции randint() и randrange() производят целые псевдослучайные числа в указанных диапазонах.

  18. Изучение списков в Python - основы и операции

    Списки в Python - это аналог массивов в других языках программирования. Однако список может содержать элементы разных типов. В терминологии Python список - это изменяемая упорядоченная структура данных. Можно заменять его элементы, добавлять и удалять их, брать срезы. В язык встроены методы для работы со списками.

  19. Изучение цикла for в Python

    Цикл for в языке программирования Python предназначен для перебора элементов структур данных (списков, словарей, кортежей, множеств) и многих других объектов. Это не цикл со счетчиком, каковым является for во многих других языках. Нередко цикл for используется совместно с функцией range(), генерирующей объекты-диапазоны.

  20. Строки в Python - методы и срезы

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

  21. Кортежи - неизменяемые структуры данных

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

  22. Словари в Python - работа с ключами и значениями

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

  23. Работа с файлами в Python

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