Дерево ошибок python

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

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

Напишем программу, которая будет считать обратные значения для целых чисел из заданного диапазона и выводить их в одну строку с разделителем ‘;’. Один из вариантов кода для решения этой задачи выглядит так:

print(";".join(str(1 / x) for x in range(int(input()), int(input()) + 1)))

Программа получилась в одну строчку за счёт использования списочных выражений. Однако при вводе диапазона чисел, включающего в себя 0 (например, от -1 до 1), программа выдаст следующую ошибку:

ZeroDivisionError: division by zero

В программе произошла ошибка «деление на ноль». Такая ошибка, возникающая при выполнении программы и останавливающая её работу, называется исключением.

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

interval = range(int(input()), int(input()) + 1)
if 0 in interval:
    print("Диапазон чисел содержит 0.")
else:
    print(";".join(str(1 / x) for x in interval))

Теперь для диапазона, включающего в себя 0, например от -2 до 2, исключения ZeroDivisionError не возникнет. Однако при вводе строки, которую невозможно преобразовать в целое число (например, «a»), будет вызвано другое исключение:

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

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

start = input()
end = input()
# Метод lstrip("-"), удаляющий символы "-" в начале строки, нужен для учёта
# отрицательных чисел, иначе isdigit() вернёт для них False
if not (start.lstrip("-").isdigit() and end.lstrip("-").isdigit()):
    print("
    ввести два числа.")
else:
    interval = range(int(start), int(end) + 1)
    if 0 in interval:
        print("Диапазон чисел содержит 0.")
    else:
        print(";".join(str(1 / x) for x in interval))

Теперь наша программа работает без ошибок и при вводе строк, которые нельзя преобразовать в целое число.

Подход, который был нами применён для предотвращения ошибок, называется Look Before You Leap (LBYL), или «Посмотри перед прыжком». В программе, реализующей такой подход, проверяются возможные условия возникновения ошибок до исполнения основного кода.

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

Существует другой подход для работы с ошибками: Easier to Ask Forgiveness than Permission (EAFP), или «Проще попросить прощения, чем разрешения». В этом подходе сначала исполняется код, а в случае возникновения ошибок происходит их обработка. Подход EAFP реализован в Python в виде обработки исключений.

Исключения в Python являются классами ошибок. В Python есть много стандартных исключений. Они имеют определённую иерархию за счёт механизма наследования классов. В документации Python версии 3.10.8 приводится следующее дерево иерархии стандартных исключений:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      |    +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- EncodingWarning
           +-- ResourceWarning

Для обработки исключения в Python используется следующий синтаксис:

try:
    <код , который может вызвать исключения при выполнении>
except <классисключения_1>:
    <код обработки исключения>
except <классисключения_2>:
    <код обработки исключения>
...
else:
    <код выполняется, если не вызвано исключение в блоке try>
finally:
    <код , который выполняется всегда>

Блок try содержит код, в котором нужно обработать исключения, если они возникнут.
При возникновении исключения интерпретатор последовательно проверяет, в каком из блоков except обрабатывается это исключение.
Исключение обрабатывается в первом блоке except, обрабатывающем класс этого исключения или базовый класс возникшего исключения.
Необходимо учитывать иерархию исключений для определения порядка их обработки в блоках except. Начинать обработку исключений следует с более узких классов исключений. Если начать с более широкого класса исключения, например Exception, то всегда при возникновении исключения будет срабатывать первый блок except.
Сравните два следующих примера. В первом порядок обработки исключений указан от производных классов к базовым, а во втором — наоборот.

Первый пример:

try:
    print(1 / int(input()))
except ZeroDivisionError:
    print("Ошибка деления на ноль.")
except ValueError:
    print("Невозможно преобразовать строку в число.")
except Exception:
    print("Неизвестная ошибка.")

При вводе значений «0» и «a» получим ожидаемый, соответствующий возникающим исключениям вывод:

Невозможно преобразовать строку в число.

и

Ошибка деления на ноль.

Второй пример:

try:
    print(1 / int(input()))
except Exception:
    print("Неизвестная ошибка.")
except ZeroDivisionError:
    print("Ошибка деления на ноль.")
except ValueError:
    print("Невозможно преобразовать строку в число.")

При вводе значений «0» и «a» получим в обоих случаях неинформативный вывод:

Неизвестная ошибка.

Необязательный блок else выполняет код в случае, если в блоке try не вызвано исключение. Добавим блок else в пример для вывода сообщения об успешном выполнении операции:

try:
    print(1 / int(input()))
except ZeroDivisionError:
    print("Ошибка деления на ноль.")
except ValueError:
    print("Невозможно преобразовать строку в число.")
except Exception:
    print("Неизвестная ошибка.")
else:
    print("Операция выполнена успешно.")

Теперь при вводе корректного значения, например «5», вывод программы будет следующим:

0.2
Операция выполнена успешно.

Блок finally выполняется всегда, даже если возникло какое-то исключение, не учтённое в блоках except, или код в этих блоках сам вызвал какое-либо исключение. Добавим в нашу программу вывод строки «Программа завершена» в конце программы даже при возникновении исключений:

try:
    print(1 / int(input()))
except ZeroDivisionError:
    print("Ошибка деления на ноль.")
except ValueError:
    print("Невозможно преобразовать строку в число.")
except Exception:
    print("Неизвестная ошибка.")
else:
    print("Операция выполнена успешно.")
finally:
    print("Программа завершена.")

Перепишем код, созданный с применением подхода LBYL, для первого примера из этого параграфа с использованием обработки исключений:

try:
    print(";".join(str(1 / x) for x in range(int(input()), int(input()) + 1)))
except ZeroDivisionError:
    print("Диапазон чисел содержит 0.")
except ValueError:
    print("Необходимо ввести два числа.")

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

Исключения можно принудительно вызывать с помощью оператора raise. Этот оператор имеет следующий синтаксис:

raise <класс исключения>(параметры)

В качестве параметра можно, например, передать строку с сообщением об ошибке.

Создание собственных исключений

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

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

  • NumbersError — базовый класс исключения;
  • EvenError — исключение, которое вызывается при наличии хотя бы одного чётного числа;
  • NegativeError — исключение, которое вызывается при наличии хотя бы одного отрицательного числа.
class NumbersError(Exception):
    pass


class EvenError(NumbersError):
    pass


class NegativeError(NumbersError):
    pass


def no_even(numbers):
    if all(x % 2 != 0 for x in numbers):
        return True
    raise EvenError("В списке не должно быть чётных чисел")


def no_negative(numbers):
    if all(x >= 0 for x in numbers):
        return True
    raise NegativeError("В списке не должно быть отрицательных чисел")


def main():
    print("Введите числа в одну строку через пробел:")
    try:
        numbers = [int(x) for x in input().split()]
        if no_negative(numbers) and no_even(numbers):
            print(f"Сумма чисел равна: {sum(numbers)}.")
    except NumbersError as e:  # обращение к исключению как к объекту
        print(f"Произошла ошибка: {e}.")
    except Exception as e:
        print(f"Произошла непредвиденная ошибка: {e}.")

        
if __name__ == "__main__":
    main()

Модули

Обратите внимание: в программе основной код выделен в функцию main. А код вне функций содержит только условный оператор и вызов функции main при выполнении условия __name__ == "__main__". Это условие проверяет, запущен ли файл как самостоятельная программа или импортирован как модуль.

Любая программа, написанная на языке программирования Python, может быть импортирована как модуль в другую программу. В идеологии Python импортировать модуль — значит полностью его выполнить. Если основной код модуля содержит вызовы функций, ввод или вывод данных без использования указанного условия __name__ == "__main__", то произойдёт полноценный запуск программы. А это не всегда удобно, если из модуля нужна только отдельная функция или какой-либо класс.

При изучении модуля itertools мы говорили о том, как импортировать модуль в программу. Покажем ещё раз два способа импорта на примере собственного модуля.

Для импорта модуля из файла, например example_module.py, нужно указать его имя, если он находится в той же папке, что и импортирующая его программа:

import example_module

Если требуется отдельный компонент модуля, например функция или класс, то импорт можно осуществить так:

from example_module import some_function, ExampleClass

Обратите внимание: при втором способе импортированные объекты попадают в пространство имён новой программы. Это означает, что они будут объектами новой программы и в программе не должно быть других объектов с такими же именами.

Рассмотрим написанное выше на примере. Пусть имеется программа module_hello.py, в которой находится функция hello(name), возвращающая строку приветствия пользователя по имени. В самой программе кроме функции присутствует вызов этой функции и печать результата её работы. Импортируем из модуля module_hello.py функцию hello(name) в другую программу program.py и также используем для вывода приветствия пользователя.

Код программы module_hello.py:

def hello(name):
    return f"Привет, {name}!"


print(hello(input("Введите своё имя: ")))

Код программы program.py:

from module_hello import hello

print(hello(input("Добрый день. Введите имя: ")))

При выполнении program.py нас ожидает неожиданное действие. Программа сначала запросит имя пользователя, а затем сделает это ещё раз, но с приветствием из program.py.

Введите своё имя: Андрей
Привет, Андрей!
Добрый день. Введите имя: Андрей
Привет, Андрей!

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

def hello(name):
    return f"Привет, {name}!"


if __name__ == "__main__":
    print(hello(input("Введите своё имя: ")))

Теперь при импорте модуля module_hello.py код в теле условного оператора выполняться не будет. А основной код этой программы выполнится только при запуске файла как отдельной программы.
Для большего удобства обычно в теле указанного условного оператора вызывают функцию main(), а основной код программы оформляют уже внутри этой функции.
Тогда наш модуль можно переписать так:

def hello(name):
    return f"Привет, {name}!"


def main():
    print(hello(input("Введите своё имя: ")))


if __name__ == "__main__":
    main()

Обратите внимание: при импорте модуля мы можем с помощью символа * указать, что необходимо импортировать все объекты. Например, так:

from some_module import *

Однако делать так крайне не рекомендуется, потому что все объекты модуля добавляются в пространство имён нашей программы, что может приводить к конфликтам.

Before Printing the Error Hierarchy let’s understand what an Exception really is? Exceptions occur even if our code is syntactically correct, however, while executing they throw an error. They are not unconditionally fatal, errors which we get while executing are called Exceptions. There are many Built-in Exceptions in Python let’s try to print them out in a hierarchy.

For printing the tree hierarchy we will use inspect module in Python. The inspect module provides useful functions to get information about objects such as modules, classes, methods, functions,  and code objects. For example, it can help you examine the contents of a class, extract and format the argument list for a function.

For building a tree hierarchy we will use inspect.getclasstree().

Syntax: inspect.getclasstree(classes, unique=False)

inspect.getclasstree() arranges the given list of classes into a hierarchy of nested lists. Where a nested list appears, it contains classes derived from the class whose entry immediately precedes the list.

If the unique argument is true, exactly one entry appears in the returned structure for each class in the given list. Otherwise, classes using multiple inheritance and their descendants will appear multiple times.

Let’s write a code for printing tree hierarchy for built-in exceptions:

Python3

import inspect

def treeClass(cls, ind = 0):

    print ('-' * ind, cls.__name__)

    for i in cls.__subclasses__():

        treeClass(i, ind + 3)

print("Hierarchy for Built-in exceptions is : ")

inspect.getclasstree(inspect.getmro(BaseException))

treeClass(BaseException)

Output:

Hierarchy for Built-in exceptions is : 
 BaseException
--- Exception
------ TypeError
------ StopAsyncIteration
------ StopIteration
------ ImportError
--------- ModuleNotFoundError
--------- ZipImportError
------ OSError
--------- ConnectionError
------------ BrokenPipeError
------------ ConnectionAbortedError
------------ ConnectionRefusedError
------------ ConnectionResetError
--------- BlockingIOError
--------- ChildProcessError
--------- FileExistsError
--------- FileNotFoundError
--------- IsADirectoryError
--------- NotADirectoryError
--------- InterruptedError
--------- PermissionError
--------- ProcessLookupError
--------- TimeoutError
--------- UnsupportedOperation
------ EOFError
------ RuntimeError
--------- RecursionError
--------- NotImplementedError
--------- _DeadlockError
------ NameError
--------- UnboundLocalError
------ AttributeError
------ SyntaxError
--------- IndentationError
------------ TabError
------ LookupError
--------- IndexError
--------- KeyError
--------- CodecRegistryError
------ ValueError
--------- UnicodeError
------------ UnicodeEncodeError
------------ UnicodeDecodeError
------------ UnicodeTranslateError
--------- UnsupportedOperation
------ AssertionError
------ ArithmeticError
--------- FloatingPointError
--------- OverflowError
--------- ZeroDivisionError
------ SystemError
--------- CodecRegistryError
------ ReferenceError
------ MemoryError
------ BufferError
------ Warning
--------- UserWarning
--------- DeprecationWarning
--------- PendingDeprecationWarning
--------- SyntaxWarning
--------- RuntimeWarning
--------- FutureWarning
--------- ImportWarning
--------- UnicodeWarning
--------- BytesWarning
--------- ResourceWarning
------ _OptionError
------ error
------ Verbose
------ Error
------ TokenError
------ StopTokenizing
------ EndOfBlock
--- GeneratorExit
--- SystemExit
--- KeyboardInterrupt

Last Updated :
20 Aug, 2020

Like Article

Save Article

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

Чтобы распечатать древовидную иерархию, мы можем использовать модуль inspect в Python. Модуль проверки используется для получения информации об объектах, таких как модули, методы классов, функции и объекты программы. Например, пользователи могут использовать его для изучения содержимого класса, извлечения и форматирования списка аргументов функции.

Чтобы построить древовидную иерархию, мы можем использовать функцию inspect.getclasstree().

Синтаксис:

 
inspect.getclasstree(classes, unique = False) 

inspect.getclasstree(): используется для организации заданного списка классов в иерархию вложенных списков. Там, где появляется вложенный список, он будет содержать классы, производные от класса, запись которого немедленно продолжит список.

Пример:

 
# For printing the hierarchy for inbuilt exceptions: 
# First, we will import the inspect module 
import inspect as ipt 
   
# Then we will create tree_class function 
def tree_class(cls, ind = 0): 
     
      # Then we will print the name of the class 
    print('-' * ind, cls.__name__) 
       
    # now, we will iterate through the subclasses 
    for K in cls.__subclasses__(): 
        tree_class(K, ind + 3) 
   
print("The Hierarchy for inbuilt exceptions is: ") 
   
# THE inspect.getmro() will return the tuple  
# of class  which is cls's base classes. 
   
#Now, we will build a tree hierarchy  
ipt.getclasstree(ipt.getmro(BaseException)) 
   
# function call 
tree_class(BaseException) 

Вывод:

The Hierarchy for inbuilt exceptions is:  
 BaseException 
---> Exception 
------> TypeError 
---------> MultipartConversionError 
---------> FloatOperation 
------> StopAsyncIteration 
------> StopIteration 
------> ImportError 
---------> ModuleNotFoundError 
---------> ZipImportError 
------> OSError 
---------> ConnectionError 
------------> BrokenPipeError 
------------> ConnectionAbortedError 
------------> ConnectionRefusedError 
------------> ConnectionResetError 
--------------> RemoteDisconnected 
---------> BlockingIOError 
---------> ChildProcessError 
---------> FileExistsError 
---------> FileNotFoundError 
---------> IsADirectoryError 
---------> NotADirectoryError 
---------> InterruptedError 
------------> InterruptedSystemCall 
---------> PermissionError 
---------> ProcessLookupError 
---------> TimeoutError 
---------> UnsupportedOperation 
---------> Error 
------------> SameFileError 
---------> SpecialFileError 
---------> ExecError 
---------> ReadError 
---------> herror 
---------> gaierror 
---------> timeout 
---------> SSLError 
------------> SSLCertVerificationError 
------------> SSLZeroReturnError 
------------> SSLWantReadError 
------------> SSLWantWriteError 
------------> SSLSyscallError 
------------> SSLEOFError 
---------> URLError 
------------> HTTPError 
------------> ContentTooShortError 
------> EOFError 
---------> IncompleteReadError 
------> RuntimeError 
---------> RecursionError 
---------> NotImplementedError 
------------> StdinNotImplementedError 
------------> ZMQVersionError 
---------> _DeadlockError 
---------> BrokenBarrierError 
---------> BrokenExecutor 
---------> SendfileNotAvailableError 
------> NameError 
---------> UnboundLocalError 
------> AttributeError 
---------> FrozenInstanceError 
------> SyntaxError 
---------> IndentationError 
------------> TabError 
------> LookupError 
---------> IndexError 
---------> KeyError 
------------> UnknownBackend 
------------> NoSuchKernel 
. 
. 
. 
. 
---> GeneratorExit 
---> SystemExit 
---> KeyboardInterrupt 
---> CancelledError 

Вывод

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

Изучаю Python вместе с вами, читаю, собираю и записываю информацию опытных программистов.

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

Исключения в языках программирования

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

Исключения разделяют на синхронные и асинхронные. Синхронные исключения могут возникнуть только в определенных местах программы. Например, если у вас есть код, который открывает файл и считывает из него данные, то исключение типа “ошибка чтения данных” может произойти только в указанном куске кода. Асинхронные исключения могут возникнуть в любой момент работы программы, они, как правило, связаны с какими-либо аппаратными проблемами, либо приходом данных. В качестве примера можно привести сигнал отключения питания.

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

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

В Python выделяют два различных вида ошибок: синтаксические ошибки и исключения.

Синтаксические ошибки в Python

Синтаксические ошибки возникают в случае если программа написана с нарушениями требований Python к синтаксису. Определяются они в процессе парсинга программы. Ниже представлен пример с ошибочным написанием функции print.

>>> for i in range(10):
    prin("hello!")

Traceback (most recent call last):
  File "<pyshell#2>", line 2, in <module>
    prin("hello!")
NameError: name 'prin' is not defined

Исключения в Python

Второй вид ошибок – это исключения. Они возникают в случае если синтаксически программа корректна, но в процессе выполнения возникает ошибка (деление на ноль и т.п.). Более подробно про понятие исключения написано выше, в разделе “исключения в языках программирования”.

Пример исключения ZeroDivisionError, которое возникает при делении на 0.

>>> a = 10
>>> b = 0
>>> c = a / b
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    c = a / b
ZeroDivisionError: division by zero

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

Иерархия исключений в Python

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

BaseException
+– SystemExit
+– KeyboardInterrupt
+– GeneratorExit
+– Exception
     +– StopIteration
     +– StopAsyncIteration
     +– ArithmeticError
     |    +– FloatingPointError
     |    +– OverflowError
     |    +– ZeroDivisionError
     +– AssertionError
     +– AttributeError
     +– BufferError
     +– EOFError
     +– ImportError
          +– ModuleNotFoundError
     +– LookupError
     |    +– IndexError
     |    +– KeyError
     +– MemoryError
     +– NameError
     |    +– UnboundLocalError
     +– OSError
     |    +– BlockingIOError
     |    +– ChildProcessError
     |    +– ConnectionError
     |    |    +– BrokenPipeError
     |    |    +– ConnectionAbortedError
     |    |    +– ConnectionRefusedError
     |    |    +– ConnectionResetError
     |    +– FileExistsError
     |    +– FileNotFoundError
     |    +– InterruptedError
     |    +– IsADirectoryError
     |    +– NotADirectoryError
     |    +– PermissionError
     |    +– ProcessLookupError
     |    +– TimeoutError
     +– ReferenceError
     +– RuntimeError
     |    +– NotImplementedError
     |    +– RecursionError
     +– SyntaxError
     |    +– IndentationError
     |         +– TabError
     +– SystemError
     +– TypeError
     +– ValueError
     |    +– UnicodeError
     |         +– UnicodeDecodeError
     |         +– UnicodeEncodeError
     |         +– UnicodeTranslateError
     +– Warning
          +– DeprecationWarning
          +– PendingDeprecationWarning
          +– RuntimeWarning
          +– SyntaxWarning
          +– UserWarning
          +– FutureWarning
          +– ImportWarning
          +– UnicodeWarning
          +– BytesWarning
          +– ResourceWarning

Как видно из приведенной выше схемы, все исключения являются подклассом исключения BaseException. Более подробно об иерархии исключений и их описании можете прочитать здесь.

Обработка исключений в Python

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

print("start")
try:
   val = int(input("input number: "))
   tmp = 10 / val
   print(tmp)
except Exception as e:
   print("Error! " + str(e))
print("stop")

В приведенной выше программе возможных два вида исключений – это ValueError, возникающее в случае, если на запрос программы “введите число”, вы введете строку, и ZeroDivisionError – если вы введете в качестве числа 0.

Вывод программы при вводе нулевого числа будет таким.

start input number: 0 Error! stop

Если бы инструкций try…except не было, то при выбросе любого из исключений программа аварийно завершится.

print("start")
val = int(input(“input number: “))
tmp = 10 / val
print(tmp)
print("stop")

Если ввести 0 на запрос приведенной выше программы, произойдет ее остановка с распечаткой сообщения об исключении.

start


input number: 0


Traceback (most recent call last):


 File “F:/work/programming/python/devpractice/tmp.py”, line 3, in <module>


   tmp = 10 / val


ZeroDivisionError: division by zero

Обратите внимание, надпись stop уже не печатается в конце вывода программы.

Согласно документу по языку Python, описывающему ошибки и исключения, оператор try работает следующим образом:

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

Для указания набора исключений, который должен обрабатывать данный блок except их необходимо перечислить в скобках (круглых) через запятую после оператора except.

Если бы мы в нашей программе хотели обрабатывать только ValueError и ZeroDivisionError, то программа выглядела бы так.

print("start")
try:
   val = int(input("input number: "))
   tmp = 10 / val
   print(tmp)
except(ValueError, ZeroDivisionError):
   print("Error!")
print("stop")

Или так, если хотим обрабатывать ValueError, ZeroDivisionError по отдельность, и, при этом, сохранить работоспособность при возникновении исключений отличных от вышеперечисленных.

print("start")
try:
   val = int(input("input number: "))
   tmp = 10 / val
   print(tmp)
except ValueError:
   print("ValueError!")
except ZeroDivisionError:
   print("ZeroDivisionError!")
except:
   print("Error!")
print("stop")

Существует возможность передать подробную информацию о произошедшем исключении в код внутри блока except.

rint("start")
try:
   val = int(input("input number: "))
   tmp = 10 / val
   print(tmp)
except ValueError as ve:
   print("ValueError! {0}".format(ve))
except ZeroDivisionError as zde:
   print("ZeroDivisionError! {0}".format(zde))
except Exception as ex:
   print("Error! {0}".format(ex))
print("stop")

Использование finally в обработке исключений

Для выполнения определенного программного кода при выходе из блока try/except, используйте оператор finally.

try:
   val = int(input("input number: "))
   tmp = 10 / val
   print(tmp)
except:
   print("Exception")
finally:
  print("Finally code")

Не зависимо от того, возникнет или нет во время выполнения кода в блоке try исключение, код в блоке finally все равно будет выполнен.

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

try:
   f = open("tmp.txt", "r")
   for line in f:
       print(line)
   f.close()
except Exception as e:
   print(e)
else:
   print("File was readed")

Генерация исключений в Python

Для принудительной генерации исключения используется инструкция raise.

Самый простой пример работы с raise может выглядеть так.

try:
   raise Exception("Some exception")
except Exception as e:
   print("Exception exception " + str(e))

Таким образом, можно “вручную” вызывать исключения при необходимости.

Пользовательские исключения (User-defined Exceptions) в Python

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

Для реализации собственного типа исключения необходимо создать класс, являющийся наследником от одного из классов исключений.

class NegValException(Exception):
   pass

try:
   val = int(input("input positive number: "))
   if val < 0:
       raise NegValException("Neg val: " + str(val))
   print(val + 10)
except NegValException as e:
  print(e)

P.S.

Если вам интересна тема анализа данных, то мы рекомендуем ознакомиться с библиотекой Pandas. На нашем сайте вы можете найти вводные уроки по этой теме. Все уроки по библиотеке Pandas собраны в книге “Pandas. Работа с данными”.
Книга: Pandas. Работа с данными

<<< Python. Урок 10. Функции в Python   Python. Урок 12. Ввод-вывод данных. Работа с файлами>>>

Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Исключения

Python: Введение в ООП

Любая программа может выполняться с ошибками. Часть ошибок связана с самим кодом. А другая часть связана с ситуациями, которые возникают довольно редко, но означают, что дальнейшее выполнение программы невозможно, если возникшую проблему никак не решить. Такие ситуации исключительны. Поэтому и механизм языка, предназначенный для работы с исключительными ситуациями, называется системой исключений (exceptions).

Пример исключительной ситуации: ошибка IndexError. Эту ошибку вы видите, когда обращаетесь к строке, списку, кортежу по индексу, который выходит за границы коллекции. Как правило, с коллекциями вы работаете с помощью цикла for, или же не забываете следить за тем, чтобы индекс не достигал длины списка. А значит ситуация выхода индекса за допустимые границы — исключительная!

Когда вы открываете файл на запись, а место на диске внезапно заканчивается или у вас не оказывается прав на запись в этот файл — все это тоже исключительные ситуации.

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

Возбуждение (raising) исключения в коде похоже на return, только на его глобальную версию, завершающую все функции в порядке, обратном тому, в котором они вызывались. Если исключение будет возбуждено, но не будет перехвачено, то есть как-то обработано, вся программа так и завершится и вы увидите распечатку трейсбэка (traceback). Там-то и будет отображена та самая ошибка IndexError (или какая-то другая).

Иерархия исключений

Исключения в современном языке программирования с богатой системной библиотекой могут быть самыми разными и представлены во множестве. Однако почти всегда исключения объединяются в иерархию исключений. Так все «ошибка ввода-вывода»/IOError или ошибка «файл не найден»/FileNotFoundError наследуются от исключения OSError («ошибки взаимодействия с Операционной Системой»), которое наследуется от Exception («просто некое исключение»). IndexError и KeyError («ключ (словаря) не найден») являются потомками LookupError («ошибка поиска чего-то»), которое является потомком Exception.

Иерархия исключений, таким образом, представляет собой дерево, корнем которого является BaseException, стволом — Exception, а дальше происходит ветвление на виды исключений, а затем — на конкретные исключения.

Зачем же нужно было придумывать эту самую иерархию исключений? Чтобы можно было «ловить» исключения как по одному (ловить IndexError), так и перехватывать целые группы (OSError).

Иерархии классов, isinstance и issubclass

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

isinstance(7, object)  # True

7 является экземпляром класса int, но int является потомком класса object, поэтому в какой-то мере семерка, и правду, является экземпляром класса object!

У isinstance есть функция-близнец issubclass. Эта проверяет родство классов:

issubclass(int, object)  # True
issubclass(IndexError, LookupError)  # True
issubclass(IndexError, Exception)  # True

Последние два примера интересны в контексте данной темы: именно так и можно проверить, что некое исключение входит в нужную группу. Иерархия исключений во многом ради возможности делать такие проверки и создавалась!

Синтаксис, наконец-то!

Вот так выглядит возбуждение исключения:

raise ValueError('Age too low!')
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# ValueError: Age too low!

Ключевое слово raise принимает в качестве аргумента экземпляр какого-либо класса, являющегося потомком BaseException. Большинство исключений принимают в качестве параметра строковое сообщение, описывающее конкретную ситуацию.

А так выглядит перехват исключений:

l = []
try:
    l[100500] = 42
except IndexError:
    print('Catched!')

# => Catched!

try: начинает блок, при выполнении которого могут возникать исключения. Следом идут одна или несколько веток except, которые описывают базовый класс исключений, которые будут перехватываться. Если возникшее исключение подошло — класс исключения оказался потомком от указанного базового класса или самим указанным классом, то будет выполнен код обработчика. В данном примере вместо ошибки мы видим печать сообщения.

Важно помнить, что если у вас указано несколько веток except, то первыми нужно указывать наиболее конкретные ветки. Иначе вы можете оказаться в ситуации вроде этой:

try:
    user = users[input('May I have your name? ')]
except Exception:
    sys.exit(1)  # молча завершаем программу
except (KeyError, IndexError):
    print('No users with such name found!')

Здесь ветка except Exception: отлавливает вообще все исключения, ведь любое конкретное исключение косвенно будет экземпляром Exception! Увы, вторая ветка except не имеет шанса хоть раз быть выполненной :(

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

Ветка finally

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

f = open('data.txt')
try:
    text = f.read()
    words = len(text.split())
finally:
    f.close()

В этом коде файл будет закрыт (f.close()) в любом случае, вне зависимости от того, произошла ошибка или нет. Если ошибка все же произошла, то сразу после блока finally выполнение кода будет прервано и исключение «всплывет» выше, но хотя бы по поводу незакрытого файла можно будет не волноваться!

Ветка finally может использоваться вместе с ветками except, но должна идти самой последней.

Получение экземпляра исключения и возбуждение уже пойманного

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

try:
    ...
except (SQLSelectError, SQLInsertError) as e:
    print(f"Query execution error: '{e.query}'")
except DBConnectionError as e:
    print(f"Can't connect to DB: '{e.status}'")
...

Но что делать, если вы перехватили исключение, сделали некие необходимые действия, а затем решили «пробросить» исключение выше. Большинству новичков приходит на ум строчка raise e. Но это плохая идея: так вы получите возбуждение нового исключения, пусть даже и представляющего старый объект, а значит в traceback местом возникновения исключения будет уже эта самая строчка raise e! Обычно вы не хотите терять информацию о месте возникновения изначальной исключительной ситуации, поэтому просто пишите raise — так будет заново возбуждено последнее перехваченное исключение!

Антипаттерны при перехвате исключений

Перехватывать исключения можно неправильно. Неправильный порядок обработчиков мы уже обсуждали выше. Есть и другая типичная ошибка, которую, к счастью, обычно находит линтер: except Exception: (или просто except:). Чем же плох такой способ поймать сразу все исключения? Тем, что можно случайно поймать то, что ловить совсем даже не нужно, например ошибку «переменная не объявлена»:

try:
    bla-bla
except:
    pass  # теперь мы не увидим, что переменная "bla" не была объявлена!

Совет: всегда перехватывайте только те исключения, которые ожидаете и собираетесь обработать именно в месте перехвата, а все остальные исключения пусть «всплывают» выше!

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

Понравилась статья? Поделить с друзьями:
  • Дерево более моложе где ошибка
  • Демон тулс ошибка проверки лицензии
  • Демон тулс ошибка драйвера
  • Деревенский стратегическая ошибка современной физики
  • Демон тулс ошибка доступ запрещен