При возникновении ошибки времени выполнения Python создает (возбуждает) специальный объект - исключение, который позволяет однозначно характеризовать возникшую ошибочную ситуацию. Выбор подходящего исключения происходит из встроенной иерархии классов-исключений (фрагмент):
BaseException (базовое исключение)
SystemExit (исключение, порождаемое функцией sys.exit() при выходе из программы)
KeyboardInterrupt (прерывании программы пользователем, Ctrl+C)
Exception (базовое несистемное исключение)
ArithmeticError (арифметическая ошибка)
FloatingPointError (неудачное выполнение операции с плавающей запятой)
OverflowError (результат арифметической операции слишком велик для представления)
ZeroDivisionError (деление на ноль)
LookupError (некорректный индекс или ключ)
IndexError (индекс не входит в диапазон элементов)
KeyError (несуществующий ключ)
MemoryError (недостаточно памяти)
NameError (не найдено переменной с таким именем)
OSError (ошибка, связанная с ОС - есть подклассы, например FileNotFoundError)
SyntaxError (синтаксическая ошибка, включает классы IndentationError и TabError)
SystemError (внутренняя ошибка)
TypeError (операция применена к объекту несоответствующего типа)
ValueError (аргумент правильного типа, но некорректного значения)
Пример встроенного возбуждения исключения:
>>> "я - строка" / 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'int'
Конструкция try
Придерживаясь идеологии «Легче попросить прощения, чем разрешения», Python предусматривает конструкцию try для обработки возникающих исключений.
tryexceptelsefinally
try: # (try строго 1)
try_ suite # код, который может выполниться с ошибкой
except exception_group1 as var1: # (except - 0 (если есть finally) и более)
except_suite1 # код, выполняемый в случае исключения 'exception_group1'
... # ссылка на исключение может быть записана в 'var1'
except exception_groupN as varN:
except_suiteN # код, выполняемый в случае исключения 'exception_groupN'
... # except-блоков может быть произвольное кол-во
else: # (else - 0 или 1)
else_suite # выполняется, если try не завершен преждевременно (например, break)
finally: # (finally - 0 или 1)
finally_suite # код, который должен выполнится всегда (была ошибка выше или нет)
Ход выполнения:
код, который потенциально может привести к ошибке, помещается в блок try;
в случае ошибки, код немедленно завершается и переходит в обработчик except (если он указан для соответствующего исключения);
после поток выполнения переходит к else (если исключений не было) и finally (в любом случае).
Наиболее простой способ обработки исключений
try:
x = int(input("Введите целое число x (для вычисления 1/x): "))
res = 1 / x
print("1/{} = {:.2f}".format(x, res))
except:
print("Произошла ошибка!")
# --------------
# Примеры вывода:
# Введите целое число x (для вычисления 1/x): 3
# 1/3 = 0.33
# Введите целое число x (для вычисления 1/x): qwerty
# Произошла ошибка!
Подобный вариант обработки исключений не рекомендуется, т.к. блок except будет перехватывать любое исключение, что не позволит точно определить ошибку в коде. Улучшить код можно, добавив обработку исключения по классу (Листинг 7.1.4).
Обработка общего класса исключенийException
try:
x = int(input("Введите целое число x (для вычисления 1/x): "))
res = 1 / x
print("1/{} = {:.2f}".format(x, res))
except Exception as err:
print("Произошла ошибка!")
print("Тип:", type(err))
print("Описание:", err)
# --------------
# Примеры вывода:
# Введите целое число x (для вычисления 1/x): 3
# 1/3 = 0.33
# Введите целое число x (для вычисления 1/x): 5.5
# Произошла ошибка!
# Тип: <class 'ValueError'>
# Описание: invalid literal for int() with base 10: '5.5'
Рекомендуемым способом обработки исключений является как можно большая конкретизация класса исключения.
Обработка конкретных классов исключений
try:
x = int(input("Введите целое число x (для вычисления 1/x): "))
res = 1 / x
print("1/{} = {:.2f}".format(x, res))
except ZeroDivisionError:
print("На ноль делить нельзя!")
except ValueError as err: # 'err' содержит ссылку на исключение
print("Будьте внимательны:", err)
except (FileExistsError, FileNotFoundError): # Исключения можно перечислять в виде кортежа
print("Этого никогда не случится - мы не работаем с файлами")
except Exception as err:
# Все, что не обработано выше и является потомком 'Exception',
# будет обработано здесь
print("Произошла ошибка!")
print("Тип:", type(err))
print("Описание:", err)
# --------------
# Примеры вывода:
# Введите целое число x (для вычисления 1/x): 3
# 1/3 = 0.33
# Введите целое число x (для вычисления 1/x): 0
# На ноль делить нельзя!
# Введите целое число x (для вычисления 1/x): qwerty
# Будьте внимательны: invalid literal for int() with base 10: 'qwerty'
Возбуждение исключений (raise)
Исключения являются не только механизмом обработки ошибок, но и удобным средством управления потоком выполнения. Так, необходимое исключение можно возбудить вручную, когда это необходимо, используя конструкцию raise.
raise
raise exception(args) # явное указание класса возбуждаемого исключения
# или
raise # 1) повторное возбуждение активного исключения (re-raise)
# внутри блока except
# 2) 'TypeError' по умолчанию
Возбуждаемое исключение может быть как встроенным (если соответствует по смыслу), так и пользовательским (создаваемым самостоятельно).
В Листинге 7.1.6 приведен пример использование оператора raise.
Листинг 7.1.6 - Использование raise для управления потоком выполнения | скачать
MIN = 1
MAX = 10
try:
x = int(input("Введите целое число от {} до {}: ".format(MIN, MAX)))
if not MIN <= x <= MAX:
# Возбудив исключение, его можно будет обработать в except
# вместе с другими похожими исключениями
raise ValueError("Число лежит вне интервала [{}; {}]!".format(MIN, MAX))
print("Спасибо!")
except ValueError as err: # 'err' содержит ссылку на исключение
print("Будьте внимательны:", err)
# --------------
# Примеры вывода:
# Введите целое число от 1 до 10: 5
# Спасибо!
# Введите целое число от 1 до 10: 15
# Будьте внимательны: Число лежит вне интервала [1; 10]!
# Введите целое число от 1 до 10: qwerty
# Будьте внимательны: invalid literal for int() with base 10: 'qwerty'
Особенности обработки исключений внутри функций
Обработка исключений аналогично производится и внутри функций, однако, необходимо писать код так, чтобы вызывающий код знал о случившемся, если это влияет на его дальнейшую работу.
Разница в обработке исключений приведена в Листинге 7.1.7.
Листинг 7.1.7 - Различная обработка исключений в функции | скачать
# Ниже представлены 3 варианта обработки исключений в функциях
# Основное правило - обработка исключений внутри возможна и нужна, однако
# вызывающий код должен также знать о случившемся, если
# влияет на дальнейшую работу
def get_1_x(x):
"""Вернуть 1/x.
Функция не обрабатывает исключения - ответственность на вызывающем коде.
"""
return 1/x
def get_2_x(x):
"""Вернуть 2/x.
Функция обрабатывает исключения, "затушив" ошибку - вызывающий код
не будет знать, сработала функция правильно или нет.
Данный способ использовать не рекомендуется!
"""
try:
return 2/x
except Exception as e:
print("Внутри произошла ошибка...", e)
def get_3_x(x):
"""Вернуть 3/x.
Функция не только обрабатывает исключения, но перевозбуждает его:
в результате вызывающий так же получает возникшее исключение.
Внутренняя обработка исключений может быть полезна, если в целом результат
функции не связан с внутренней ошибкой.
"""
try:
return 3/x
except Exception as e:
print("Внутри произошла ошибка...", e)
raise
funcs = (get_1_x, get_2_x, get_3_x)
# Вызываем каждую функцию с "ошибочным" параметром
for func in funcs:
try:
print("-" * 50)
print("Запущена функция:", func.__name__)
print(func(0))
except Exception as e:
print("Произошла ошибка: {}.".format(e))
# -------------
# Пример вывода:
# --------------------------------------------------
# Запущена функция: get_1_x
# Произошла ошибка: division by zero.
# --------------------------------------------------
# Запущена функция: get_2_x
# Внутри произошла ошибка... division by zero
# None
# --------------------------------------------------
# Запущена функция: get_3_x
# Внутри произошла ошибка... division by zero
# Произошла ошибка: division by zero.