With python обработка ошибок

Differentiating between the possible origins of exceptions raised from a compound with statement

Differentiating between exceptions that occur in a with statement is tricky because they can originate in different places. Exceptions can be raised from either of the following places (or functions called therein):

  • ContextManager.__init__
  • ContextManager.__enter__
  • the body of the with
  • ContextManager.__exit__

For more details see the documentation about Context Manager Types.

If we want to distinguish between these different cases, just wrapping the with into a try .. except is not sufficient. Consider the following example (using ValueError as an example but of course it could be substituted with any other exception type):

try:
    with ContextManager():
        BLOCK
except ValueError as err:
    print(err)

Here the except will catch exceptions originating in all of the four different places and thus does not allow to distinguish between them. If we move the instantiation of the context manager object outside the with, we can distinguish between __init__ and BLOCK / __enter__ / __exit__:

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        with mgr:
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        # At this point we still cannot distinguish between exceptions raised from
        # __enter__, BLOCK, __exit__ (also BLOCK since we didn't catch ValueError in the body)
        pass

Effectively this just helped with the __init__ part but we can add an extra sentinel variable to check whether the body of the with started to execute (i.e. differentiating between __enter__ and the others):

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        entered_body = False
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        else:
            # At this point we know the exception came either from BLOCK or from __exit__
            pass

The tricky part is to differentiate between exceptions originating from BLOCK and __exit__ because an exception that escapes the body of the with will be passed to __exit__ which can decide how to handle it (see the docs). If however __exit__ raises itself, the original exception will be replaced by the new one. To deal with these cases we can add a general except clause in the body of the with to store any potential exception that would have otherwise escaped unnoticed and compare it with the one caught in the outermost except later on — if they are the same this means the origin was BLOCK or otherwise it was __exit__ (in case __exit__ suppresses the exception by returning a true value the outermost except will simply not be executed).

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    entered_body = exc_escaped_from_body = False
    try:
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
            except Exception as err:  # this exception would normally escape without notice
                # we store this exception to check in the outer `except` clause
                # whether it is the same (otherwise it comes from __exit__)
                exc_escaped_from_body = err
                raise  # re-raise since we didn't intend to handle it, just needed to store it
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        elif err is exc_escaped_from_body:
            print('BLOCK raised:', err)
        else:
            print('__exit__ raised:', err)

Alternative approach using the equivalent form mentioned in PEP 343

PEP 343 — The «with» Statement specifies an equivalent «non-with» version of the with statement. Here we can readily wrap the various parts with try ... except and thus differentiate between the different potential error sources:

import sys

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        value = type(mgr).__enter__(mgr)
    except ValueError as err:
        print('__enter__ raised:', err)
    else:
        exit = type(mgr).__exit__
        exc = True
        try:
            try:
                BLOCK
            except TypeError:
                pass
            except:
                exc = False
                try:
                    exit_val = exit(mgr, *sys.exc_info())
                except ValueError as err:
                    print('__exit__ raised:', err)
                else:
                    if not exit_val:
                        raise
        except ValueError as err:
            print('BLOCK raised:', err)
        finally:
            if exc:
                try:
                    exit(mgr, None, None, None)
                except ValueError as err:
                    print('__exit__ raised:', err)

Usually a simpler approach will do just fine

The need for such special exception handling should be quite rare and normally wrapping the whole with in a try ... except block will be sufficient. Especially if the various error sources are indicated by different (custom) exception types (the context managers need to be designed accordingly) we can readily distinguish between them. For example:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...

Differentiating between the possible origins of exceptions raised from a compound with statement

Differentiating between exceptions that occur in a with statement is tricky because they can originate in different places. Exceptions can be raised from either of the following places (or functions called therein):

  • ContextManager.__init__
  • ContextManager.__enter__
  • the body of the with
  • ContextManager.__exit__

For more details see the documentation about Context Manager Types.

If we want to distinguish between these different cases, just wrapping the with into a try .. except is not sufficient. Consider the following example (using ValueError as an example but of course it could be substituted with any other exception type):

try:
    with ContextManager():
        BLOCK
except ValueError as err:
    print(err)

Here the except will catch exceptions originating in all of the four different places and thus does not allow to distinguish between them. If we move the instantiation of the context manager object outside the with, we can distinguish between __init__ and BLOCK / __enter__ / __exit__:

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        with mgr:
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        # At this point we still cannot distinguish between exceptions raised from
        # __enter__, BLOCK, __exit__ (also BLOCK since we didn't catch ValueError in the body)
        pass

Effectively this just helped with the __init__ part but we can add an extra sentinel variable to check whether the body of the with started to execute (i.e. differentiating between __enter__ and the others):

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        entered_body = False
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        else:
            # At this point we know the exception came either from BLOCK or from __exit__
            pass

The tricky part is to differentiate between exceptions originating from BLOCK and __exit__ because an exception that escapes the body of the with will be passed to __exit__ which can decide how to handle it (see the docs). If however __exit__ raises itself, the original exception will be replaced by the new one. To deal with these cases we can add a general except clause in the body of the with to store any potential exception that would have otherwise escaped unnoticed and compare it with the one caught in the outermost except later on — if they are the same this means the origin was BLOCK or otherwise it was __exit__ (in case __exit__ suppresses the exception by returning a true value the outermost except will simply not be executed).

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    entered_body = exc_escaped_from_body = False
    try:
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
            except Exception as err:  # this exception would normally escape without notice
                # we store this exception to check in the outer `except` clause
                # whether it is the same (otherwise it comes from __exit__)
                exc_escaped_from_body = err
                raise  # re-raise since we didn't intend to handle it, just needed to store it
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        elif err is exc_escaped_from_body:
            print('BLOCK raised:', err)
        else:
            print('__exit__ raised:', err)

Alternative approach using the equivalent form mentioned in PEP 343

PEP 343 — The «with» Statement specifies an equivalent «non-with» version of the with statement. Here we can readily wrap the various parts with try ... except and thus differentiate between the different potential error sources:

import sys

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        value = type(mgr).__enter__(mgr)
    except ValueError as err:
        print('__enter__ raised:', err)
    else:
        exit = type(mgr).__exit__
        exc = True
        try:
            try:
                BLOCK
            except TypeError:
                pass
            except:
                exc = False
                try:
                    exit_val = exit(mgr, *sys.exc_info())
                except ValueError as err:
                    print('__exit__ raised:', err)
                else:
                    if not exit_val:
                        raise
        except ValueError as err:
            print('BLOCK raised:', err)
        finally:
            if exc:
                try:
                    exit(mgr, None, None, None)
                except ValueError as err:
                    print('__exit__ raised:', err)

Usually a simpler approach will do just fine

The need for such special exception handling should be quite rare and normally wrapping the whole with in a try ... except block will be sufficient. Especially if the various error sources are indicated by different (custom) exception types (the context managers need to be designed accordingly) we can readily distinguish between them. For example:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...

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

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

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

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

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

Ошибки могут быть разных видов:

  • Синтаксические
  • Недостаточно памяти
  • Ошибки рекурсии
  • Исключения

Разберем их по очереди.

Синтаксические ошибки (SyntaxError)

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

Рассмотрим на примере.

a = 8
b = 10
c = a b
File "", line 3
 c = a b
       ^
SyntaxError: invalid syntax

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

Недостаточно памяти (OutofMemoryError)

Ошибки памяти чаще всего связаны с оперативной памятью компьютера и относятся к структуре данных под названием “Куча” (heap). Если есть крупные объекты (или) ссылки на подобные, то с большой долей вероятности возникнет ошибка OutofMemory. Она может появиться по нескольким причинам:

  • Использование 32-битной архитектуры Python (максимальный объем выделенной памяти невысокий, между 2 и 4 ГБ);
  • Загрузка файла большого размера;
  • Запуск модели машинного обучения/глубокого обучения и много другое;

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

Но поскольку Python использует архитектуру управления памятью из языка C (функция malloc()), не факт, что все процессы восстановятся — в некоторых случаях MemoryError приведет к остановке. Следовательно, обрабатывать такие ошибки не рекомендуется, и это не считается хорошей практикой.

Ошибка рекурсии (RecursionError)

Эта ошибка связана со стеком и происходит при вызове функций. Как и предполагает название, ошибка рекурсии возникает, когда внутри друг друга исполняется много методов (один из которых — с бесконечной рекурсией), но это ограничено размером стека.

Все локальные переменные и методы размещаются в стеке. Для каждого вызова метода создается стековый кадр (фрейм), внутрь которого помещаются данные переменной или результат вызова метода. Когда исполнение метода завершается, его элемент удаляется.

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

def recursion():
    return recursion()

recursion()
---------------------------------------------------------------------------

RecursionError                            Traceback (most recent call last)

 in 
----> 1 recursion()


 in recursion()
      1 def recursion():
----> 2     return recursion()


... last 1 frames repeated, from the frame below ...


 in recursion()
      1 def recursion():
----> 2     return recursion()


RecursionError: maximum recursion depth exceeded

Ошибка отступа (IndentationError)

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

Пример:

for i in range(10):
    print('Привет Мир!')
  File "", line 2
    print('Привет Мир!')
        ^
IndentationError: expected an indented block

Исключения

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

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

Ошибка типа (TypeError)

a = 2
b = 'PythonRu'
a + b
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

 in 
      1 a = 2
      2 b = 'PythonRu'
----> 3 a + b


TypeError: unsupported operand type(s) for +: 'int' and 'str'

Ошибка деления на ноль (ZeroDivisionError)

10 / 0
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

 in 
----> 1 10 / 0


ZeroDivisionError: division by zero

Есть разные типы исключений в Python и их тип выводится в сообщении: вверху примеры TypeError и ZeroDivisionError. Обе строки в сообщениях об ошибке представляют собой имена встроенных исключений 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

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

  • Try: он запускает блок кода, в котором ожидается ошибка.
  • Except: здесь определяется тип исключения, который ожидается в блоке try (встроенный или созданный).
  • Else: если исключений нет, тогда исполняется этот блок (его можно воспринимать как средство для запуска кода в том случае, если ожидается, что часть кода приведет к исключению).
  • Finally: вне зависимости от того, будет ли исключение или нет, этот блок кода исполняется всегда.

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

Ошибка прерывания с клавиатуры (KeyboardInterrupt)

Исключение KeyboardInterrupt вызывается при попытке остановить программу с помощью сочетания Ctrl + C или Ctrl + Z в командной строке или ядре в Jupyter Notebook. Иногда это происходит неумышленно и подобная обработка поможет избежать подобных ситуаций.

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

try:
    inp = input()
    print('Нажмите Ctrl+C и прервите Kernel:')
except KeyboardInterrupt:
    print('Исключение KeyboardInterrupt')
else:
    print('Исключений не произошло')

Исключение KeyboardInterrupt

Стандартные ошибки (StandardError)

Рассмотрим некоторые базовые ошибки в программировании.

Арифметические ошибки (ArithmeticError)

  • Ошибка деления на ноль (Zero Division);
  • Ошибка переполнения (OverFlow);
  • Ошибка плавающей точки (Floating Point);

Все перечисленные выше исключения относятся к классу Arithmetic и вызываются при ошибках в арифметических операциях.

Деление на ноль (ZeroDivisionError)

Когда делитель (второй аргумент операции деления) или знаменатель равны нулю, тогда результатом будет ошибка деления на ноль.

try:  
    a = 100 / 0
    print(a)
except ZeroDivisionError:  
    print("Исключение ZeroDivisionError." )
else:  
    print("Успех, нет ошибок!")
Исключение ZeroDivisionError.

Переполнение (OverflowError)

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

try:  
    import math
    print(math.exp(1000))
except OverflowError:  
    print("Исключение OverFlow.")
else:  
    print("Успех, нет ошибок!")
Исключение OverFlow.

Ошибка утверждения (AssertionError)

Когда инструкция утверждения не верна, вызывается ошибка утверждения.

Рассмотрим пример. Предположим, есть две переменные: a и b. Их нужно сравнить. Чтобы проверить, равны ли они, необходимо использовать ключевое слово assert, что приведет к вызову исключения Assertion в том случае, если выражение будет ложным.

try:  
    a = 100
    b = "PythonRu"
    assert a == b
except AssertionError:  
    print("Исключение AssertionError.")
else:  
    print("Успех, нет ошибок!")

Исключение AssertionError.

Ошибка атрибута (AttributeError)

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

class Attributes(obj):
    a = 2
    print(a)

try:
    obj = Attributes()
    print(obj.attribute)
except AttributeError:
    print("Исключение AttributeError.")

2
Исключение AttributeError.

Ошибка импорта (ModuleNotFoundError)

Ошибка импорта вызывается при попытке импортировать несуществующий (или неспособный загрузиться) модуль в стандартном пути или даже при допущенной ошибке в имени.

import nibabel
---------------------------------------------------------------------------

ModuleNotFoundError                       Traceback (most recent call last)

 in 
----> 1 import nibabel


ModuleNotFoundError: No module named 'nibabel'

Ошибка поиска (LookupError)

LockupError выступает базовым классом для исключений, которые происходят, когда key или index используются для связывания или последовательность списка/словаря неверна или не существует.

Здесь есть два вида исключений:

  • Ошибка индекса (IndexError);
  • Ошибка ключа (KeyError);

Ошибка ключа

Если ключа, к которому нужно получить доступ, не оказывается в словаре, вызывается исключение KeyError.

try:  
    a = {1:'a', 2:'b', 3:'c'}  
    print(a[4])  
except LookupError:  
    print("Исключение KeyError.")
else:  
    print("Успех, нет ошибок!")

Исключение KeyError.

Ошибка индекса

Если пытаться получить доступ к индексу (последовательности) списка, которого не существует в этом списке или находится вне его диапазона, будет вызвана ошибка индекса (IndexError: list index out of range python).

try:
    a = ['a', 'b', 'c']  
    print(a[4])  
except LookupError:  
    print("Исключение IndexError, индекс списка вне диапазона.")
else:  
    print("Успех, нет ошибок!")
Исключение IndexError, индекс списка вне диапазона.

Ошибка памяти (MemoryError)

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

Ошибка имени (NameError)

Ошибка имени возникает, когда локальное или глобальное имя не находится.

В следующем примере переменная ans не определена. Результатом будет ошибка NameError.

try:
    print(ans)
except NameError:  
    print("NameError: переменная 'ans' не определена")
else:  
    print("Успех, нет ошибок!")
NameError: переменная 'ans' не определена

Ошибка выполнения (Runtime Error)

Ошибка «NotImplementedError»
Ошибка выполнения служит базовым классом для ошибки NotImplemented. Абстрактные методы определенного пользователем класса вызывают это исключение, когда производные методы перезаписывают оригинальный.

class BaseClass(object):
    """Опередляем класс"""
    def __init__(self):
        super(BaseClass, self).__init__()
    def do_something(self):
	# функция ничего не делает
        raise NotImplementedError(self.__class__.__name__ + '.do_something')

class SubClass(BaseClass):
    """Реализует функцию"""
    def do_something(self):
        # действительно что-то делает
        print(self.__class__.__name__ + ' что-то делает!')

SubClass().do_something()
BaseClass().do_something()

SubClass что-то делает!



---------------------------------------------------------------------------

NotImplementedError                       Traceback (most recent call last)

 in 
     14
     15 SubClass().do_something()
---> 16 BaseClass().do_something()


 in do_something(self)
      5     def do_something(self):
      6         # функция ничего не делает
----> 7         raise NotImplementedError(self.__class__.__name__ + '.do_something')
      8
      9 class SubClass(BaseClass):


NotImplementedError: BaseClass.do_something

Ошибка типа (TypeError)

Ошибка типа вызывается при попытке объединить два несовместимых операнда или объекта.

В примере ниже целое число пытаются добавить к строке, что приводит к ошибке типа.

try:
    a = 5
    b = "PythonRu"
    c = a + b
except TypeError:
    print('Исключение TypeError')
else:
    print('Успех, нет ошибок!')

Исключение TypeError

Ошибка значения (ValueError)

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

В этом примере встроенная операция float получат аргумент, представляющий собой последовательность символов (значение), что является недопустимым значением для типа: число с плавающей точкой.

try:
    print(float('PythonRu'))
except ValueError:
    print('ValueError: не удалось преобразовать строку в float: 'PythonRu'')
else:
    print('Успех, нет ошибок!')
ValueError: не удалось преобразовать строку в float: 'PythonRu'

Пользовательские исключения в Python

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

Это можно сделать, создав новый класс, который будет наследовать из класса Exception в Python.

class UnAcceptedValueError(Exception):   
    def __init__(self, data):    
        self.data = data
    def __str__(self):
        return repr(self.data)

Total_Marks = int(input("Введите общее количество баллов: "))
try:
    Num_of_Sections = int(input("Введите количество разделов: "))
    if(Num_of_Sections < 1):
        raise UnAcceptedValueError("Количество секций не может быть меньше 1")
except UnAcceptedValueError as e:
    print("Полученная ошибка:", e.data)

Введите общее количество баллов: 10
Введите количество разделов: 0
Полученная ошибка: Количество секций не может быть меньше 1

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

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

У использования исключений есть свои побочные эффекты, как, например, то, что программы с блоками try-except работают медленнее, а количество кода возрастает.

Дальше пример, где модуль Python timeit используется для проверки времени исполнения 2 разных инструкций. В stmt1 для обработки ZeroDivisionError используется try-except, а в stmt2if. Затем они выполняются 10000 раз с переменной a=0. Суть в том, чтобы показать разницу во времени исполнения инструкций. Так, stmt1 с обработкой исключений занимает больше времени чем stmt2, который просто проверяет значение и не делает ничего, если условие не выполнено.

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

import timeit
setup="a=0"
stmt1 = '''
try:
    b=10/a
except ZeroDivisionError:
    pass'''

stmt2 = '''
if a!=0:
    b=10/a'''

print("time=",timeit.timeit(stmt1,setup,number=10000))
print("time=",timeit.timeit(stmt2,setup,number=10000))

time= 0.003897680000136461
time= 0.0002797570000439009

Выводы!

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

Обработка исключений — один из основных факторов, который делает код готовым к развертыванию. Это простая концепция, построенная всего на 4 блоках: try выискивает исключения, а except их обрабатывает.

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

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

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

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

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

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

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

В 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. Ввод-вывод данных. Работа с файлами>>>

Содержание:развернуть

  • Как устроен механизм исключений
  • Как обрабатывать исключения в Python (try except)
  • As — сохраняет ошибку в переменную

  • Finally — выполняется всегда

  • Else — выполняется когда исключение не было вызвано

  • Несколько блоков except

  • Несколько типов исключений в одном блоке except

  • Raise — самостоятельный вызов исключений

  • Как пропустить ошибку

  • Исключения в lambda функциях
  • 20 типов встроенных исключений в Python
  • Как создать свой тип Exception

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

  • Синтаксические ошибки — возникают, когда написанное выражение не соответствует правилам языка (например, написана лишняя скобка);
  • Исключения — возникают во время выполнения программы (например, при делении на ноль).

Синтаксические ошибки исправить просто (если вы используете IDE, он их подсветит). А вот с исключениями всё немного сложнее — не всегда при написании программы можно сказать возникнет или нет в данном месте исключение. Чтобы приложение продолжило работу при возникновении проблем, такие ошибки нужно перехватывать и обрабатывать с помощью блока try/except.

Как устроен механизм исключений

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

💁‍♂️ Пример: напишем скрипт, в котором функция ожидает число, а мы передаём сроку (это вызовет исключение «TypeError»):

def b(value):
print("-> b")
print(value + 1) # ошибка тут

def a(value):
print("-> a")
b(value)

a("10")

> -> a
> -> b
> Traceback (most recent call last):
> File "test.py", line 11, in <module>
> a("10")
> File "test.py", line 8, in a
> b(value)
> File "test.py", line 3, in b
> print(value + 1)
> TypeError: can only concatenate str (not "int") to str

В данном примере мы запускаем файл «test.py» (через консоль). Вызывается функция «a«, внутри которой вызывается функция «b«. Все работает хорошо до сточки print(value + 1). Тут интерпретатор понимает, что нельзя конкатенировать строку с числом, останавливает выполнение программы и вызывает исключение «TypeError».

Далее ошибка передается по цепочке в обратном направлении: «b» → «a» → «test.py«. Так как в данном примере мы не позаботились обработать эту ошибку, вся информация по ошибке отобразится в консоли в виде Traceback.

Traceback (трассировка) — это отчёт, содержащий вызовы функций, выполненные в определенный момент. Трассировка помогает узнать, что пошло не так и в каком месте это произошло.

Traceback лучше читать снизу вверх ↑

Пример Traceback в Python

В нашем примере Traceback содержится следующую информацию (читаем снизу вверх):

  1. TypeError — тип ошибки (означает, что операция не может быть выполнена с переменной этого типа);
  2. can only concatenate str (not "int") to str — подробное описание ошибки (конкатенировать можно только строку со строкой);
  3. Стек вызова функций (1-я линия — место, 2-я линия — код). В нашем примере видно, что в файле «test.py» на 11-й линии был вызов функции «a» со строковым аргументом «10». Далее был вызов функции «b». print(value + 1) это последнее, что было выполнено — тут и произошла ошибка.
  4. most recent call last — означает, что самый последний вызов будет отображаться последним в стеке (в нашем примере последним выполнился print(value + 1)).

В Python ошибку можно перехватить, обработать, и продолжить выполнение программы — для этого используется конструкция try ... except ....

Как обрабатывать исключения в Python (try except)

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

Например, вот как можно обработать ошибку деления на ноль:

try:
a = 7 / 0
except:
print('Ошибка! Деление на 0')

Здесь в блоке try находится код a = 7 / 0 — при попытке его выполнить возникнет исключение и выполнится код в блоке except (то есть будет выведено сообщение «Ошибка! Деление на 0»). После этого программа продолжит свое выполнение.

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

try:
a = 7 / 0
except ZeroDivisionError:
print('Ошибка! Деление на 0')

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

try:
a = 7 / 0
except Exception:
print('Любая ошибка!')

As — сохраняет ошибку в переменную

Перехваченная ошибка представляет собой объект класса, унаследованного от «BaseException». С помощью ключевого слова as можно записать этот объект в переменную, чтобы обратиться к нему внутри блока except:

try:
file = open('ok123.txt', 'r')
except FileNotFoundError as e:
print(e)

> [Errno 2] No such file or directory: 'ok123.txt'

В примере выше мы обращаемся к объекту класса «FileNotFoundError» (при выводе на экран через print отобразится строка с полным описанием ошибки).

У каждого объекта есть поля, к которым можно обращаться (например если нужно логировать ошибку в собственном формате):

import datetime

now = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S")

try:
file = open('ok123.txt', 'r')
except FileNotFoundError as e:
print(f"{now} [FileNotFoundError]: {e.strerror}, filename: {e.filename}")

> 20-11-2021 18:42:01 [FileNotFoundError]: No such file or directory, filename: ok123.txt

Finally — выполняется всегда

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

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

В следующем примере откроем файл и обратимся к несуществующей строке:

file = open('ok.txt', 'r')

try:
lines = file.readlines()
print(lines[5])
finally:
file.close()
if file.closed:
print("файл закрыт!")

> файл закрыт!
> Traceback (most recent call last):
> File "test.py", line 5, in <module>
> print(lines[5])
> IndexError: list index out of range

Даже после исключения «IndexError», сработал код в секции finally, который закрыл файл.

p.s. данный пример создан для демонстрации, в реальном проекте для работы с файлами лучше использовать менеджер контекста with.

Также можно использовать одновременно три блока try/except/finally. В этом случае:

  • в try — код, который может вызвать исключения;
  • в except — код, который должен выполниться при возникновении исключения;
  • в finally — код, который должен выполниться в любом случае.

def sum(a, b):
res = 0

try:
res = a + b
except TypeError:
res = int(a) + int(b)
finally:
print(f"a = {a}, b = {b}, res = {res}")

sum(1, "2")

> a = 1, b = 2, res = 3

Else — выполняется когда исключение не было вызвано

Иногда нужно выполнить определенные действия, когда код внутри блока try не вызвал исключения. Для этого используется блок else.

Допустим нужно вывести результат деления двух чисел и обработать исключения в случае попытки деления на ноль:

b = int(input('b = '))
c = int(input('c = '))
try:
a = b / c
except ZeroDivisionError:
print('Ошибка! Деление на 0')
else:
print(f"a = {a}")

> b = 10
> c = 1
> a = 10.0

В этом случае, если пользователь присвоит переменной «с» ноль, то появится исключение и будет выведено сообщение «‘Ошибка! Деление на 0′», а код внутри блока else выполняться не будет. Если ошибки не будет, то на экране появятся результаты деления.

Несколько блоков except

В программе может возникнуть несколько исключений, например:

  1. Ошибка преобразования введенных значений к типу float («ValueError»);
  2. Деление на ноль («ZeroDivisionError»).

В Python, чтобы по-разному обрабатывать разные типы ошибок, создают несколько блоков except:

try:
b = float(input('b = '))
c = float(input('c = '))
a = b / c
except ZeroDivisionError:
print('Ошибка! Деление на 0')
except ValueError:
print('Число введено неверно')
else:
print(f"a = {a}")

> b = 10
> c = 0
> Ошибка! Деление на 0

> b = 10
> c = питон
> Число введено неверно

Теперь для разных типов ошибок есть свой обработчик.

Несколько типов исключений в одном блоке except

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

try:
b = float(input('b = '))
c = float(input('c = '))
a = b / c
except (ZeroDivisionError, ValueError) as er:
print(er)
else:
print('a = ', a)

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

Raise — самостоятельный вызов исключений

Исключения можно генерировать самостоятельно — для этого нужно запустить оператор raise.

min = 100
if min > 10:
raise Exception('min must be less than 10')

> Traceback (most recent call last):
> File "test.py", line 3, in <module>
> raise Exception('min value must be less than 10')
> Exception: min must be less than 10

Перехватываются такие сообщения точно так же, как и остальные:

min = 100

try:
if min > 10:
raise Exception('min must be less than 10')
except Exception:
print('Моя ошибка')

> Моя ошибка

Кроме того, ошибку можно обработать в блоке except и пробросить дальше (вверх по стеку) с помощью raise:

min = 100

try:
if min > 10:
raise Exception('min must be less than 10')
except Exception:
print('Моя ошибка')
raise

> Моя ошибка
> Traceback (most recent call last):
> File "test.py", line 5, in <module>
> raise Exception('min must be less than 10')
> Exception: min must be less than 10

Как пропустить ошибку

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

try:
a = 7 / 0
except ZeroDivisionError:
pass

Исключения в lambda функциях

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

20 типов встроенных исключений в Python

Иерархия классов для встроенных исключений в Python выглядит так:

BaseException
SystemExit
KeyboardInterrupt
GeneratorExit
Exception
ArithmeticError
AssertionError
...
...
...
ValueError
Warning

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

  • SystemExit — системное исключение, вызываемое функцией sys.exit() во время выхода из приложения;
  • KeyboardInterrupt — возникает при завершении программы пользователем (чаще всего при нажатии клавиш Ctrl+C);
  • GeneratorExit — вызывается методом close объекта generator;
  • Exception — исключения, которые можно и нужно обрабатывать (предыдущие были системными и их трогать не рекомендуется).

От Exception наследуются:

1 StopIteration — вызывается функцией next в том случае если в итераторе закончились элементы;

2 ArithmeticError — ошибки, возникающие при вычислении, бывают следующие типы:

  • FloatingPointError — ошибки при выполнении вычислений с плавающей точкой (встречаются редко);
  • OverflowError — результат вычислений большой для текущего представления (не появляется при операциях с целыми числами, но может появиться в некоторых других случаях);
  • ZeroDivisionError — возникает при попытке деления на ноль.

3 AssertionError — выражение, используемое в функции assert неверно;

4 AttributeError — у объекта отсутствует нужный атрибут;

5 BufferError — операция, для выполнения которой требуется буфер, не выполнена;

6 EOFError — ошибка чтения из файла;

7 ImportError — ошибка импортирования модуля;

8 LookupError — неверный индекс, делится на два типа:

  • IndexError — индекс выходит за пределы диапазона элементов;
  • KeyError — индекс отсутствует (для словарей, множеств и подобных объектов);

9 MemoryError — память переполнена;

10 NameError — отсутствует переменная с данным именем;

11 OSError — исключения, генерируемые операционной системой:

  • ChildProcessError — ошибки, связанные с выполнением дочернего процесса;
  • ConnectionError — исключения связанные с подключениями (BrokenPipeError, ConnectionResetError, ConnectionRefusedError, ConnectionAbortedError);
  • FileExistsError — возникает при попытке создания уже существующего файла или директории;
  • FileNotFoundError — генерируется при попытке обращения к несуществующему файлу;
  • InterruptedError — возникает в том случае если системный вызов был прерван внешним сигналом;
  • IsADirectoryError — программа обращается к файлу, а это директория;
  • NotADirectoryError — приложение обращается к директории, а это файл;
  • PermissionError — прав доступа недостаточно для выполнения операции;
  • ProcessLookupError — процесс, к которому обращается приложение не запущен или отсутствует;
  • TimeoutError — время ожидания истекло;

12 ReferenceError — попытка доступа к объекту с помощью слабой ссылки, когда объект не существует;

13 RuntimeError — генерируется в случае, когда исключение не может быть классифицировано или не подпадает под любую другую категорию;

14 NotImplementedError — абстрактные методы класса нуждаются в переопределении;

15 SyntaxError — ошибка синтаксиса;

16 SystemError — сигнализирует о внутренне ошибке;

17 TypeError — операция не может быть выполнена с переменной этого типа;

18 ValueError — возникает когда в функцию передается объект правильного типа, но имеющий некорректное значение;

19 UnicodeError — исключение связанное с кодирование текста в unicode, бывает трех видов:

  • UnicodeEncodeError — ошибка кодирования;
  • UnicodeDecodeError — ошибка декодирования;
  • UnicodeTranslateError — ошибка перевода unicode.

20 Warning — предупреждение, некритическая ошибка.

💭 Посмотреть всю цепочку наследования конкретного типа исключения можно с помощью модуля inspect:

import inspect

print(inspect.getmro(TimeoutError))

> (<class 'TimeoutError'>, <class 'OSError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)

📄 Подробное описание всех классов встроенных исключений в Python смотрите в официальной документации.

Как создать свой тип Exception

В Python можно создавать свои исключения. При этом есть одно обязательное условие: они должны быть потомками класса Exception:

class MyError(Exception):
def __init__(self, text):
self.txt = text

try:
raise MyError('Моя ошибка')
except MyError as er:
print(er)

> Моя ошибка


С помощью try/except контролируются и обрабатываются ошибки в приложении. Это особенно актуально для критически важных частей программы, где любые «падения» недопустимы (или могут привести к негативным последствиям). Например, если программа работает как «демон», падение приведет к полной остановке её работы. Или, например, при временном сбое соединения с базой данных, программа также прервёт своё выполнение (хотя можно было отловить ошибку и попробовать соединиться в БД заново).

Вместе с try/except можно использовать дополнительные блоки. Если использовать все блоки описанные в статье, то код будет выглядеть так:

try:
# попробуем что-то сделать
except (ZeroDivisionError, ValueError) as e:
# обрабатываем исключения типа ZeroDivisionError или ValueError
except Exception as e:
# исключение не ZeroDivisionError и не ValueError
# поэтому обрабатываем исключение общего типа (унаследованное от Exception)
# сюда не сходят исключения типа GeneratorExit, KeyboardInterrupt, SystemExit
else:
# этот блок выполняется, если нет исключений
# если в этом блоке сделать return, он не будет вызван, пока не выполнился блок finally
finally:
# этот блок выполняется всегда, даже если нет исключений else будет проигнорирован
# если в этом блоке сделать return, то return в блоке

Подробнее о работе с исключениями в Python можно ознакомиться в официальной документации.

Перевод статьи Learning (not) to Handle Exceptions

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

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

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

Содержание

  • Инструкции Try/Except
  • Перехват исключений заданного типа
  • Повторное возбуждение исключений
  • Использование исключений в исключениях
  • Обработка нескольких исключений
  • Блок finally
  • Блок else
  • Работа с traceback (трассировка)
  • Применение пользовательских исключений
  • Примеры хороших практик использования пользовательских исключений
  • Добавляем к исключениям аргументы
  • Выводы

Инструкции Try/Except

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

f = open('my_file.dat')
data = f.readfile()
print('Data loaded')

И если вы выполните этот код и файла с именем my_file.dat в рабочей директории нет, то увидите следующее сообщение:

FileNotFoundError: [Errno 2] No such file or directory: 'my_file.dat'

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

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

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

try:
    f = open('my_file.dat')
    f.readfile()
    print('Loaded data')
except:
    print('Data not loaded')

Если теперь мы его запустим на выполнение, то увидим на экране сообщение: Data not loaded (Данные не загружены). И это замечательно! Теперь наша программа аварийно не завершает свою работу, и мы можем корректно закрыть соединение или прервать связь с нашим устройством. Однако мы всё ещё не знаем причину, по которой данные не были загружены.

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

open('my_file.dat')
f.readfile()
print('Loaded data')

После его выполнения получим в консоли ошибку вида:

AttributeError: '_io.TextIOWrapper' object has no attribute 'readfile'

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

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

Чтобы вы имели представление о важности правильной обработки ошибок, я приведу вам пример, ситуации, возникшей при эксплуатации прикладного приложения, которое поставлялось вместе со сложным лабораторным оборудованием. Эта программа управляет прибором Nano Sight и имеет очень приятный пользовательский интерфейс. Однако при сохранении данных, если выбранное имя файла содержало точку, данные не сохранялись. К сожалению, в этом случае данные безвозвратно терялись, и пользователь никогда не узнал бы, что причина возникновения проблемы на самом деле проста: наличие символа . в имени файла.

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

Перехват исключений заданного типа

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

try:
    file = open('my_file.dat')
    data = file.readfile()
    print('Data Loaded')
except FileNotFoundError:
    print('This file doesn't exist')

Если вы запустите этот скрипт, а файла my_file.dat в рабочей директории нет, то он выведет на экран сообщение, что файл не существует, и программа продолжит работу. Однако, если файл существует, вы увидите исключение связанное с отсутствием у объекта file метода readfile (такого метода конечно же не существует — это преднамеренная ошибка).

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

try:
    file = open('my_file.dat')
    data = file.readfile()
    print('Data Loaded')
except FileNotFoundError:
    file = open('my_file.dat', 'w')
    print('File created')
file.close()

Если вы запустите этот скрипт, то увидите, что теперь файл my_file.dat будет создан. Если вы запустите скрипт во второй раз, то будет возбуждено исключение из-за отсутствия метода readfile.

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

try:
    file = open('my_file.dat')
    data = file.readfile()
    print('Data Loaded')
except:
    file = open('my_file.dat', 'w')
    print('File created')

Если вы его внимательно проанализируете, то поймете, что, даже если файла my_file.dat существует, будет возбуждено исключение, ввиду отсутствия метода readfile. Затем будет выполнен код в блоке except. В этом блоке наша программа создаст новый файл my_file.dat, даже если он уже существует, и, следовательно, вы потеряете все информацию, которая в нем до этого хранилась. Что же мы можем сделать для того, что бы избежать этого рассмотрим далее.

Повторное возбуждение исключений

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

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

#Данные сохранены. Начало нового цикла опроса датчиков
try:
    temp = instrument.readtemp() # пытаемся опросить датчик температуры
    
except:
    remove_last_line(data_file) 
# если датчик не отвечает то удалим последнюю запись в файле, записанную в текущем цикле

    raise 
# возбудим это исключение снова для того, чтобы пользователь был оповещен о возникшей ошибке  
     
save_temperature(temp) 
# если все нормально сохраняем показания датчика в файл

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

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

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

Рассмотрим следующий пример кода.

try:
# код, который мы проверяем
except:
# при перехвате любого типа исключений
# этот блок кода будет выполнен
    raise
except Exception_classname1:
# при перехвате исключения с классом Exception_name1
# этот блок кода будет выполнен
except Exception_classname2:
# при перехвате исключения с классом Exception_name2
# этот блок кода будет выполнен

Как видно первый блок except будет перехватывать абсолютно все типы исключений и соответственно при возникновении любой исключительной ситуации код внутри этого блока будет выполнен всегда. Следующие блоки except будут выполняться только при перехвате исключений соответствующих классов Exception_classname1 и Exception_classname2 .

А значит в первый блок except можно поместить код, который необходимо выполнять при возникновении исключительной ситуации любого вида. После чего инициируем возбуждение исключения того же типа с помощью инструкции raise.

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

С синтаксисом использования инструкции except вы можете ознакомиться по ссылке. С классами исключительных ситуаций по этой ссылке.

Использование исключений в исключениях

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

try:
    file = open(filename)
    data = file.readfile()
except FileNotFoundError:
    file = open(filename, 'w')

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

filename  =  'my_data.dat' 
try : 
    [ ... ]

Если вы запустите этот код, то заметите, что он ведет себя точно так, как ожидалось. Однако, если вы укажете пустое имя файла:

filename = ''
try:
    [...]

Вы увидите выведенное на экран сообщение с большим количеством информации о возникшей ошибке, содержащей лишь одну важную строку:

During handling of the above exception, another exception occurred:

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

Решить возникшую причину можно, вложив в наш блок except еще один блок инструкций try/except или же предварительно проверять валидность значения переменной filename перед вызовом инструкции open. Но самый правильный способ избежать возникновения этой ситуации мы рассмотрим ниже.

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

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

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

file = open('my_data.dat', 'a')
file.readfile()

То получите сообщение:

AttributeError: '_io.TextIOWrapper' object has no attribute 'readfile'

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

Для решения нашей задачи необходимо перехватить исключения 2 типов: AttributeError и FileNotFound. Поэтому наш код будет иметь вид:

filename = 'my_data.dat'

try:
    file = open(filename)
    data = file.readfile()
except FileNotFoundError:
    file = open(filename, 'w')
    print('Created file')
except AttributeError:
    print('Attribute Error')

Теперь мы знаем как можно обрабатывать в программе несколько типов исключений. Отметим, что если в какой-либо строке кода внутри блока try возникает исключение, то остальная часть кода в этом блоке не будет выполнена. То есть если файла my_data.dat не существует то, следующая строка кода, то есть data = file.readfile() не будет выполняться.

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

В нашем же случае при первом запуске кода файла my_data.dat не существует, то есть будет инициализироваться исключение типа — FileNotFoundError. Файл будет создан и в консоли мы получим сообщение Created file. При повторном запуске нашего кода будет возбуждено исключение класса AttributeError. Так файл существует, но созданный объект file не имеет метода readfile(), то попытка его вызвать завершится возбуждением соответствующего исключения.

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

filename = 'my_data.dat'

try:
    file = open(filename)
    data = file.read()
    important_data = data[0]
except FileNotFoundError:
    file = open(filename, 'w')
    print('Created file')
except AttributeError:
    print('Attribute Error')
except:
    print('Unhandled exception')

В случае если файл my_data.dat существует, но он пуст, у нас возникнет еще одна проблема при попытке доступа к содержимому data[0] (получить первый символ строки, в которую мы прочитали содержимое файла). Мы не готовы к обработке этого исключения и поэтому напечатаем лишь сообщение о необработанном исключении (Unhandled exception). Тем не менее, было бы более полезно сообщать пользователю, какое же исключение было возбуждено. Для этого мы можем использовать следующий приём:

filename = 'my_data.dat'

try:
    file = open(filename)
    data = file.read()
    important_data = data[0]
except Exception as e:
    print('Unhandled exception')
    print(e)

Результатом выполнения этого кода будет следующее сообщение:

Unhandled exception
string index out of range

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

filename = 'my_data.dat'

try:
    file = open(filename)
    data = file.read()
    important_data = data[0]
except Exception as e:
    print('Unhandled exception')
    if isinstance(e, IndexError):
        print(e)
        data = 'Information'
        important_data = data[0]

print(important_data)

После запуска этого кода на выполнение, он напечатает в консоли первую букву строки Information, то есть I.

Отметим, что изучение этого примера кода имеет своей целью продемонстрировать возможности использования информации о возбужденном исключении, в ущерб практической значимости. Как мы можем видеть, его главный недостаток заключается в том, что переменная important_data будет в конечном итоге не определена. Это произойдет в случае если файл my_data.dat не существует. И поэтому мы получим соответствующее сообщение об ошибке:

NameError: name 'important_data' is not defined

Чтобы не допустить таких ситуаций мы можем добавить еще один блок в последовательность блоков-перехватчиков except, это блок с инструкцией finally.

Блок finally

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

filename = 'my_data.dat'

try:
    file = open(filename)
    data = file.read()
    important_data = data[0]
except Exception as e:
    if isinstance(e, IndexError):
        print(e)
        data = 'Information'
        important_data = data[0]
    else:
        print('Unhandled exception')
finally:
    important_data = 'A'

print(important_data)

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

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

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

filename = 'my_data.dat'

try:
    print('In the try block')
    file = open(filename)
    data = file.read()
    important_data = data[0]
except FileNotFoundError:
    print('File not found, creating one')
    file = open(filename, 'w')
finally:
    print('Finally, closing the file')
    file.close()
    important_data = 'A'

print(important_data)

Сначала запустим код, для ситуации когда файла my_data.dat не существует. И увидим следующий результат его выполнения:

In the try block
File not found, creating one
Finally, closing the file

Итак, мы видим , что код последовательно выполнялся в блоках: сначала в try, затем во всех блоках except и наконец в finally. Если вы снова запустите код, то файл уже будет существовать, и, следовательно, результат его выполнения будет совершенно другим:

In the try block
Finally, closing the file
Traceback (most recent call last):
  File "JJ_exceptions.py", line 7, in 
    important_data = data[0]
IndexError: string index out of range

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

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

Блок else

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

filename = 'my_data.dat'

try:
    file = open(filename)
except FileNotFoundError:
    print('File not found, creating one')
    file = open(filename, 'w')
else:
    data = file.read()
    important_data = data[0]

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

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

try:
    file = open(filename)
    new_filename = file.readline()
except FileNotFoundError:
    print('File not found, creating one')
    file = open(filename, 'w')
else:
    new_file = open(new_filename)
    data = new_file.read()

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

Теперь объединим всё, что мы узнали о приемах обработки и инициализации исключений вместе:

try:
    file = open(filename)
    new_filename = file.readline()
except FileNotFoundError:
    print('File not found, creating one')
    file = open(filename, 'w')
else:
    new_file = open(new_filename)
    data = new_file.read()
finally:
    file.close()

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

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

Работа с traceback (трассировка)

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

Traceback (most recent call last):
  File "P_traceback.py", line 13, in 
    file = open(filename)
FileNotFoundError: [Errno 2] No such file or directory: 'my_data.dat'

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

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

Во-вторых из нашего примера сообщения сразу можно определить путь к файлу, который стал причиной возникшей ошибки, а также номер строки исходного кода скрипта, выполнение которой привело к ошибке. Если затем вы откроете файл исходного кода в текстовом редакторе и перейдете к этой строке, то увидите, что это строка, содержащую следующие инструкции: file = open(filename). И наконец из сообщения, вы можете узнать тип возникшего исключения FileNotFoundError, также как мы это делали ранее.

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

import traceback

filename = 'my_data.dat'

try:
    file = open(filename)
    data = file.read()
except FileNotFoundError:
    traceback.print_exc()

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

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

Применение пользовательских исключений

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

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

Начнем с определения нашей функции:

def average(x, y):
    return (x + y)/2

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

def average(x, y):
    if x<=0 or y<=0:
        raise Exception('Both x and y should be positive')
    return (x + y)/2

Если вы попробуете это вызвать нашу функцию с отрицательным параметром, в консоли вы увидите следующее:

Exception: Both x and y should be positive

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

class NonPositiveError(Exception):
    pass

def average(x, y):
    if x <= 0 or y <= 0:
        raise NonPositiveError('Both x and y should be positive')
    return (x + y) / 2

Объявляем класс нашего исключения, который будет наследовать от общего класса Exception. На данном этапе, мы просто будем выполнять инструкцию pass в теле нашего класса исключения.

Если мы запустим приведенный выше код с отрицательным значением входного параметра функции, то получим:

NonPositiveError: Both x and y should be positive

Теперь если вы захотите далее в коде перехватывать ваше пользовательское исключение типа NonPositiveError, то будете делать так как мы делали это ранее. Единственным отличием будет то, что пользовательские исключения по умолчанию недоступны, и их следует импортировать. Например, следующим образом:

from exceptions import NonPositiveError
from tools import average

try:
    avg = average(1, -2)
except NonPositiveError:
    avg = 0

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

Примеры хороших практик использования пользовательских исключений

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

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

class MyException(BaseException):
    pass

class NonPositiveIntegerError(MyException):
    pass

class TooBigIntegerError(MyException):
    pass

def average(x, y):
    if x<=0 or y<=0: raise NonPositiveIntegerError('Either x or y is not positive') if x>10 or y>10:
        raise TooBigIntegerError('Either x or y is too large')
    return (x+y)/2

try:
    average(1, -1)
except MyException as e:
    print(e)

try:
    average(11, 1)
except MyException as e:
    print(e)

try:
    average('a', 'b')
except MyException as e:
    print(e)

print('Done')

Сначала определим свой класс исключений MyException, которой станет нашим базовым классом. Затем определим два класса исключений NonPositiveIntegerError и TooBigIntegerError, которые наследуют от MyException . Далее определим функцию average, но на этот раз в ней будет инициализироваться два разных типа исключений соответствующих случаям: если одно или оба из чисел, передаваемых в качестве аргумента, отрицательны или больше 10.

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

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

TypeError: '<=' not supported between instances of 'str' and 'int'

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

Добавляем к исключениям аргументы

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

class MyException(BaseException):
    pass

class NonPositiveIntegerError(MyException):
    def __init__(self, x, y):
        super(NonPositiveIntegerError, self).__init__()
        if x<=0 and y<=0:
            self.msg = 'Both x and y are negative: x={}, y={}'.format(x, y)
        elif x<=0:
            self.msg = 'Only x is negative: x={}'.format(x)
        elif y<=0:
            self.msg = 'Only y is negative: y={}'.format(y)

    def __str__(self):
        return self.msg


def average(x, y):
    if x<=0 or y<=0:
        raise NonPositiveIntegerError(x, y)
    return (x+y)/2

try:
    average(1, -1)
except MyException as e:
    print(e)

Как видно из этого примера, инструкция возбуждения исключения принимает два аргумента, x и y, и генерирует на их основе форматированное сообщение. Отметим, что в нашем примере сообщение об ошибке может содержать информацию о том, что аргументы, переданные в функцию average, могут быть как оба отрицательны, так и о каждом непосредственно.

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

Ключевая часть в объявлении нашего пользовательского класса исключений находится в конце его объявления — это метод __str__. Этот метод отвечает непосредственно за то, что появляется на экране, когда вы используете инструкцию print(e) в блоке except . В нашем примере мы просто возвращаем сообщение, генерированное методом __init__, но многие разработчики предпочитают генерировать сообщение в методе __str__ на основе параметров, переданных в конструктор класса.

Выводы

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

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

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

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

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

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

In this article we will discuss error handling using Python With Statements Try/Except/Finally statements, show how to use these in combination, and compare how it works to try/catch code blocks in other languages.

What is error handling?

Error handling is when you put in some extra code to tell your script what to do when things don’t go totally to plan. perhaps you try to open a file that isn’t there. Or perhaps a user puts in unexpected input.

Without any error handling, your program or script will simply crash, throw an error, and quit running. It is important to at least put in a minimal amount of error handling to ensure that your script/program will run reliably.

Try/Catch Statements

In many languages, you use Try/Catch statements for error handling. In C# for example, you might write some code that looks like this:

Try{
string text = System.IO.File.ReadAllText(@"C:UsersPublicTestFolderWriteText.txt");

}
Catch(exception e){
console.writeline(e);
}

The above code will attempt to read the txt file. If it cannot read the file, it will throw an exception. The catch code block then catches that exception into variable e. And uses the console.writeline method to print the exception onto the console. That way you can see what happened.

If you didn’t put in the try/catch blocks, the exception would have still been shown on the screen, but the application would have crashed and you have had to re-launch it. Sometimes on your computer, you might get an error about an un-caught exception right before your program closes. Those are cases like this one where the system.io.file.readalltext method was not in a try block of code.

Python Try, Exception & Finally Statements

Python does not have try/catch blocks. Instead, python has try/exception blocks. They really do the same thing but have different names.

Let’s look at an example written in python:

try:
  f = open(“test.txt", 'r')
  data = f.read()
  print("Trying")
except:
  print("Fiddlesticks! Failed")
finally:
  print("Finally!")
  print("All Done")

In the above script, we start the try statement and we attempt to execute the code in that section. If it succeeds, it will print the text trying. It will then jump down to the finally section and print Finally! followed by “All Done” on the console.

If the above script is not able to open the test.txt file, it will throw an exception and print “FIddleSticks!” to the console, followed by Finally.

The next logical question about these statements are what are the roles of each section?

  • The Try code block is the code you really want to execute.
  • The exception code block is the bit of code that you want to execute in the event you get an error while executing the code in the try code block.
  • The Finally code block is code that you want to execute regardless of the outcome.

More helpful errors

You may find that simply writing fiddlesticks when you have an error is not all that helpful. You need to capture the error so you can write it to a log file, or maybe you want to display the error on the screen.

Lets try executing this code in our python script:

f = open(“test.txt", 'r')
data = f.read()
print(data)

The above script will read test.txt and print the contents to the console. Unfortunately, if test.txt does not exist, you will get an error like this one:IOError: [Errno 2] No such file or directory: ‘test.txt’

Notice the type of error is IOError. That is helpful because we can create an exception block of code specifically around IOErrors. Lets look at how we would write that code:

try:
  f = open(“test.txt", 'r')
  data = f.read()
  print(data)
Except IOError as e:
  Print(e)
except:
  print("Fiddlesticks! Failed")
finally:
  print("Finally!")
print("All Done")

The above code will attempt to run what is in the try block. If it failed with an IOError, it will run the code in the except block. In this case, it will print out the error saying something about how it could not open or close a file. It will then run the finally code block when everything is finished.

If we want to have different logic for different kinds of exceptions, we could keep adding similar code like the code below.  Notice we call out each type of exception. Then we have the option to have a different remediation step for each exception type.

try:
  f = open("test.txt", 'r')
    Try:
      data = f.read()
      print(data)
    except IOError as e:
      print(e)
    except ValueError as e:
      print(e)
    except EOFError as e:
      print(e)
    Except:
      print(“unknown error”)
    Finally:
      f.close()
except:
  print("Fiddlesticks! Failed")
finally:
  print("All Done")

In the case of our example above, we are doing the exact same logic for each exception, so we might as well consolidate the exception handling into a single exception line.  That would look like this:

try:
  f = open("test.txt", 'r')
  data = f.read()
  print(data)
except (IOError, ValueError, EOFError) as e:
  print(e)
except:
  print("Fiddlesticks! Failed")
finally:
  print("All Done")

In the above example, we will print out the exception if it matches IOError, Valueerror, or EOFError. If it does not match any of those, it will print out Fiddlesticks. Here are a few of the most common exceptions you may want to handle:

  • IOError – file cannot be opened
  • ImportError – cannot find the specified module
  • EOFError – When the input reaches the end of a file and no more data can be read
  • ValueError – function receives an argument that has the right type but an invalid value
  • KeyboardInterrupt – User hits the interrupt key (Ctrl+D or Ctrl+C)

Or if you want a more comprehensive list of Python Exceptions You check look here.

Creating Custom Exceptions

In the previous section, we. were focused on handling exceptions using the exceptions that are built-in to Python. However, as you are developing your application, you will most likely encounter situations where you want to handle exceptions a bit differently. This is when you would create your own custom exceptions.

To handle your own custom Exceptions, you have to create a class for each exception type. Then put in some code for what to do when that exception has occurred. The most basic of exception classes looks like this:

class MyError(Exception):
    pass

raise MyError("Test Exception!")

**Note the pass statement is there just for syntax reasons. You could put additional code there instead.

If you run the above code, you will see output that says Test Exception, like this:

Now that we know the basics to create our own custom exception. Let’s start with a new example. We have a basic math function that just adds numbers together and returns the sum:

def addnumbers(x,y):
    return x+y

print(addnumbers(3,2))

When we run the code above, the output is the number 5. Now let’s say that we want to throw an exception if someone passes in the number 3. We can create a custom exception that lets the user of our function know that we don’t allow the number 3 as an input. Then we can add some code to check if the number 3 was passed in, and raise an exception in that case.

class BadNumbersError(Exception):
    pass

def addnumbers(x,y):
    if x ==3:
        raise BadNumbersError("We don't like the number 3")
    return x+y

print(addnumbers(3,2))

In the code above, you can see we created a class called BadNumbrersError. Then in our function, we added a check. If x==3, then raise a BadNumbersError exception and pass in the text “We don’t like the number 3”.

Next, we call our function and pass in values of 3 and 2. WIthout this new exception, the output would be the number 5. But now that we have added in our exception when you run the code, you should see what our new custom exception looks like.

As you can see, we trigger our exception and present a message to the user that says that we don’t like the number 3 when calling this function.

Python With statements

With statements can be used with try/catch statements to reduce the amount of code you need to write for handling different kinds of errors.

With statements call the __Enter__ and __Exit__ functions that are part of a given class. An example of this is with the File open class.

To properly handle errors when opening files, you would need some code similar to this:

try:
  f = open(“test.txt", 'r')
    Try:
      data = f.read()
      print(data)
    except IOError as e:
      print(e)
    Except:
      print(“unknown error”)
    Finally:
      f.close()
except:
  print("Fiddlesticks! Failed")
finally:
  print("All Done")

Before we get into the with statements, lets examine this code a bit. In the previous sections, we wrote some code to catch exceptions when opening a file. But the other issue is what happens if we have an issue after the file is already open. We need to make sure we close the file. We could put that in the finally section at the very bottom. But that will throw an exception if the original file never successfully opened. The result is this big mess of nested try/except statements to hopefully catch all of the different scenarios you may encounter.

Lucky for us, the makers of Python came out with a With Statement. Like I said previously, a with statement has an __enter__ and an __exit__ function that it calls at the beginning and the end of the statement. This allows some of that code to be removed from the big try/except mess I demonstrated above. An example of a with statement can be seen below:

with open(“test.txt”,r) as f:
text=f.read()
Print(text)

The above text will still throw an exception if test.txt does not exist. However, we no longer have to remember to call f.close when we are all finished with the file. If we do have the file open and an error occurs, the with statement will handle closing out the file handles for me. What this means is we can use the with statement in conjunction with our try/except code blocks to give us cleaner looking code. Let’s look at another example:

try:
  with open(“test.txt", 'r’) as f:
    data = f.read()
    print(data)
Except IOError as e:
  Print(e)
except:
  print("Fiddlesticks! Failed")
finally:
  print("Finally!")
print("All Done")

Notice the above example looks a lot like our original example. It is the same number of lines, and it is still very readable. However, it gives us similar functionality to the second example with the nested try/except loops.

Summary

In today’s article we discussed What is Error handling, What is the role of Try/Catch code blocks. How to set up exceptions, how to create our own custom extensions, and what a with statement does for us.

Error handling is a very important part of writing good software. Software without proper error handling will be unstable, and may not give good output when invalid input is entered into it.

Исключения¶

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

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

Ошибки¶

Рассмотрим простой вызов функции print. Что, если мы ошибочно напишем
print как Print? Обратите внимание на заглавную букву. В этом случае
Python поднимает синтаксическую ошибку.

>>> Print('Привет, Мир!')
Traceback (most recent call last):
    File "<pyshell#0>", line 1, in <module>
    Print('Привет, Мир!')
NameError: name 'Print' is not defined
>>> print('Привет, Мир!')
Привет, Мир!

Обратите внимание, что была поднята ошибка NameError, а также указано место,
где была обнаружена ошибка. Так в данном случае действует обработчик ошибок.

Исключения¶

Попытаемся считать что-либо от пользователя. Нажмите Сtrl-D (или
Ctrl+Z в Windows) и посмотрите, что произойдёт.

>>> s = input('Введите что-нибудь --> ')
Введите что-нибудь -->
Traceback (most recent call last):
    File "<pyshell#2>", line 1, in <module>
    s = input('Введите что-нибудь --> ')
EOFError: EOF when reading a line

Python поднимает ошибку с именем EOFError, что означает, что он обнаружил
символ конца файла (который вводится при помощи Ctrl-D) там, где не
ожидал.

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

Обрабатывать исключения можно при помощи оператора try..except1. При
этом все обычные команды помещаются внутрь try-блока, а все обработчики
исключений — в except-блок.

Пример: (сохраните как try_except.py)

try:
    text = input('Введите что-нибудь --> ')
except EOFError:
    print('Ну зачем вы сделали мне EOF?')
except KeyboardInterrupt:
    print('Вы отменили операцию.')
else:
    print('Вы ввели {0}'.format(text))

Вывод:

$ python3 try_except.py
Введите что-нибудь -->     # Нажмите ctrl-d
Ну зачем вы сделали мне EOF?

$ python3 try_except.py
Введите что-нибудь -->     # Нажмите ctrl-c
Вы отменили операцию.

$ python3 try_except.py
Введите что-нибудь --> без ошибок
Вы ввели без ошибок

Как это работает:

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

Помните, что для каждого выражения try должно быть хотя бы одно
соответствующее выражение except. Иначе какой смысл был бы в блоке try?

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

Можно также добавить пункт else к соответствующему блоку try..except.
Этот пункт будет выполнен тогда, когда исключений не возникает.

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

Вызов исключения¶

Исключение можно поднять при помощи оператора raise2, передав ему
имя ошибки/исключения, а также объект исключения, который нужно выбросить.

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

Пример: (сохраните как raising.py)

class ShortInputException(Exception):
    '''Пользовательский класс исключения.'''
    def __init__(self, length, atleast):
        Exception.__init__(self)
        self.length = length
        self.atleast = atleast

try:
    text = input('Введите что-нибудь --> ')
    if len(text) < 3:
        raise ShortInputException(len(text), 3)
    # Здесь может происходить обычная работа
except EOFError:
    print('Ну зачем вы сделали мне EOF?')
except ShortInputException as ex:
    print('ShortInputException: Длина введённой строки — {0}; 
        ожидалось, как минимум, {1}'.format(ex.length, ex.atleast))
else:
    print('Не было исключений.')

Вывод:

$ python3 raising.py
Введите что-нибудь --> а
ShortInputException: Длина введённой строки — 1; ожидалось, как минимум, 3

$ python3 raising.py
Введите что-нибудь --> абв
Не было исключений.

Как это работает:

Здесь мы создаём наш собственный тип исключения. Этот новый тип исключения
называется ShortInputException. Он содержит два поля: length,
хранящее длину введённого текста, и atleast, указывающее, какую
минимальную длину текста ожидала программа.

В пункте except мы указываем класс ошибки ShortInputException,
который будет сохранён как3 переменная ex, содержащая соответствующий
объект ошибки/исключения. Это аналогично параметрам и аргументам при вызове
функции. Внутри этого пункта except мы используем поля length и
atleast объекта исключения для вывода необходимых сообщений пользователю.

Try .. Finally¶

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

Сохраните как finally.py:

import time

try:
    f = open('poem.txt')
    while True: # наш обычный способ читать файлы
        line = f.readline()
        if len(line) == 0:
            break
        print(line, end='')
        time.sleep(2) # Пусть подождёт некоторое время
except KeyboardInterrupt:
    print('!! Вы отменили чтение файла.')
finally:
    f.close()
    print('(Очистка: Закрытие файла)')

Вывод:

$ python3 finally.py
Программировать весело
Если работа скучна,
Чтобы придать ей весёлый тон -
!! Вы отменили чтение файла.
(Очистка: Закрытие файла)

Как это работает:

Здесь мы производим обычные операции чтения из файла, но в данном случае
добавляем двухсекундный сон после вывода каждой строки при помощи функции
time.sleep, чтобы программа выполнялась медленно (ведь Python очень быстр
от природы). Во время выполнения программы нажмите ctrl-c, чтобы
прервать/отменить выполнение программы.

Пронаблюдайте, как при этом выдаётся исключение KeyboardInterrupt, и
программа выходит. Однако, прежде чем программа выйдет, выполняется пункт
finally, и файловый объект будет всегда закрыт.

Оператор with¶

Типичной схемой является запрос некоторого ресурса в блоке try с последующим
освобождением этого ресурса в блоке finally. Для того, чтобы сделать это
более «чисто», существует оператор with4:

Сохраните как using_with.py:

with open("poem.txt") as f:
    for line in f:
        print(line, end='')

Как это работает:

Вывод должен быть таким же, как и в предыдущем примере. Разница лишь в том,
что здесь мы используем функцию open с оператором with — этим мы
оставляем автоматическое закрытие файла под ответственность with open.

За кулисами происходит следующее. Существует некий протокол, используемый
оператором with. Он считывает объект, возвращаемый оператором open.
Назовём его в данном случае «thefile».

Перед запуском блока кода, содержащегося в нём, оператор with всегда
вызывает функцию thefile.__enter__, а также всегда вызывает
thefile.__exit__ после завершения выполнения этого блока кода.

Так что код, который мы бы написали в блоке finally, будет автоматически
обработан методом __exit__. Это избавляет нас от необходимости повторно
в явном виде указывать операторы try..finally.

Более обширное рассмотрение этой темы выходит за рамки настоящей книги,
поэтому для более исчерпывающего объяснения см. :pep:343.

Резюме¶

Мы обсудили использование операторов try..except и try..finally. Мы
также увидели, как создавать наши собственные типы исключений и как их вызывать.

Далее мы ознакомимся со стандартной библиотекой Python.

Курс по Python: https://stepik.org/course/100707

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

FileNotFoundError

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

Для обработки
подобных ошибок (или, как говорят, исключений) существует специальная группа
операторов:

try / except / finally

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

Формально,
операторы try / except / finally имеют,
следующий синтаксис (определение):

try:

       
блок операторов

       
критического кода

except
[исключение]:

       
блок операторов

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

finally:

       
блок операторов

       
всегда исполняемых

       
вне зависимости, от

        возникновения исключения

И в нашем случае
их можно записать, так:

try:
    file = open("my_file.txt", encoding='utf-8')
    s = file.readlines()
    print(s)
    file.close()
except FileNotFoundError:
    print("Невозможно открыть файл")

Смотрите, здесь
функция open() находится
внутри блока try, поэтому, если
возникнет исключение FileNotFoundError, то выполнение программы перейдет в блок
except и отобразится
строка «Невозможно открыть файл». Иначе, мы прочитаем все строки из файла,
отобразим их в консоли и закроем файл.

Однако, и в
такой реализации не все гладко. В момент считывания информации из файла (в
методе readlines()) также может
возникнуть исключение из-за невозможности считывания информации из файла.
Поэтому, совсем надежно можно было бы реализовать эту программу, следующим
образом:

try:
    file = open("my_file.txt", encoding='utf-8')
 
    try:
        s = file.readline()
        print(s)
    finally:
        file.close()
        
except FileNotFoundError:
     print("Невозможно открыть файл")
except: 
    print("Ошибка при работе с файлом")

Мы здесь
прописываем еще один вложенный блок try, который будет учитывать все
возможные исключения и при их возникновении мы обязательно перейдем в блок finally для закрытия
файла. Это важная процедура – любой ранее открытый файл (функцией open()) следует
закрывать, даже при возникновении исключений. И вот такая конструкция try / finally нам гарантирует
его закрытие, что бы ни произошло в момент работы с ним. Но блок try / finally не отлавливает
исключения, поэтому они передаются внешнему блоку try и здесь мы
должны их обработать. Я сделал это через второй блок except, в котором не
указал никакого типа исключений. В результате, он будет реагировать на любые не
обработанные ошибки, то есть, в нашем случае – на любые ошибки, кроме FileNotFoundError.

Менеджер контекста для файлов

Более я не буду
углубляться в работу блоков try / except / finally. Приведенного
материала пока вполне достаточно, а в заключение этого занятия расскажу о
замене блока try / finally так называемым
файловым менеджером контекста, как это принято делать в программах на Python.

Так вот, в языке
Python существует
специальный оператор with, который, можно воспринимать как аналог
конструкции try / finally и в случае
работы с файлами записывается, следующим образом:

try:
    with open("my_file.txt", encoding='utf-8') as file:
        s = file.readlines()
        print( s )
 
except FileNotFoundError:
     print("Невозможно открыть файл")
except:
    print("Ошибка при работе с файлом")

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

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

try:
    with open("my_file.txt", encoding='utf-8') as file:
        s = file.readlines()
        print( s )
 
except FileNotFoundError:
     print("Невозможно открыть файл")
except:
    print("Ошибка при работе с файлом")
finally:
    print(file.closed)

После запуска
программы видим значение True, то есть, файл был закрыт. Даже если
произойдет критическая ошибка, например, вызовем функцию int() для строки s:

то, снова видим
значение True – файл был
закрыт. Вот в этом удобство использования менеджера контекста при работе с
файлами.

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

Курс по Python: https://stepik.org/course/100707

Видео по теме

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

Содержание страницы:
1. Чтение файла 
    1.2. Чтение больших файлов и работа с ними
    1.3. Анализ текста из файла
2. Запись в файл
    2.1. Запись в пустой файл
    2.2. Многострочная запись в файл
    2.3. Присоединение данных к файлу
3. Исключения
    3.1. Блоки try-except
    3.2. Блоки try-except-else
    3.3. Блоки try-except с текстовыми файлами
    3.4. Ошибки без уведомления пользователя

1. Чтение файла в Python

В файлах может содержаться любой объем данных, начиная от небольшого рассказа и до сохранения истории погоды за столетия. Чтение файлов особенно актуально для приложений, предназначенных для анализа данных. Приведем пример простой программы, которая открывает файл и выводит его содержимое на экран. В примере я буду использовать файл с числом «Пи» с точностью до 10 знаков после запятой. Скачать этот файл можно прямо здесь ( pi_10.txt ) или самим создать текстовый файл и сохранить под любым именем. Пример программы, которая открывает файл и выводит содержимое на экран:

with open(‘pi_10.txt’) as file_pi:
    digits = file_pi.read()
print(digits)

Код начинается с ключевого слова with. При использование ключевого слова with используемый файл открывается с помощью функции open(), а закрывается автоматически после завершения блока with и вам не придется в конце вызывать функцию close(). Файлы можно открывать и закрывать явными вызовами open() и close(). Функция open() получает один аргумент — имя открываемого файла, в нашем случае ‘pi_10.txt’. Python ищет указанный файл в каталоге, где хранится файл текущей программы. Функция open() возвращает объект, представляющий файл ‘pi_10.txt’. Python сохраняет этот объект в переменной file_pi .  

После появления объекта, представляющего файл ‘pi_10.txt’, используется метод read(), который читает все содержимое файла и сохраняет его в одной строке в переменной contents. В конце с помощью функции print содержимое выводится на экран. Запустив этот файл, мы получим данные, находящиеся в нашем файле ‘pi_10.txt’.

3.1415926535

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

  •  Относительный путь. 

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

with open(‘files/имя_файла.txt’) as file:

  • Абсолютный путь. 

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

file_path = ‘/Users/Desktop/files/имя_файла.txt’
with open(file_path) as file:

С абсолютными путями можно читать файлы из любого каталога вашей системы. 

1.2. Чтение больших файлов на Python и работа с ними

В первом примере был файл с 10 знаками после запятой. Теперь давайте проанализируем файл с миллионом знаков числа «Пи» после запятой. Скачать число «Пи» с миллионом знаков после запятой можно отсюда( ‘pi_1000000.txt’ ). Изменять код из первого примера не придется, просто заменим файл, который должен читать Python. 

Выведем на экран первые 100 знаков после запятой. Добавим в конец функцию len, чтобы узнать длину файла

3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679

1000002

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

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

with open(‘pi_1000000.txt‘) as file_pi:
    digits = file_pi.read()
birthday = input(«Введите дату дня рождения: «)
if birthday  in digits:
    print(«Ваш день рождение входит в число ‘Пи'»)
else:
    print(«Ваш день рождение не входит в число ‘Пи'»)

Начало программы не изменилось, читаем файл и сохраняем данные в переменной digits. Далее запрашиваем данные от пользователя с помощью функции input и сохраняем в переменную birstday. Затем проверяем вхождение birstday в digits с помощью команды if-else. Запустив несколько раз программу, получим результат:

Введите дату дня рождения: 260786
Ваш день рождение не входит в число ‘Пи’

Введите дату дня рождения: 260884
Ваш день рождение входит в число ‘Пи’

В зависимости от введенных данных мы получили результат вхождения или не вхождения дня рождения в число «Пи»

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

1.3. Анализ текста из файла на Python

Python может анализировать текстовые файлы, содержащие целые книги. Возьмем книгу «Алиса в стране чудес» и попробуем подсчитать количество слов в книге. Текстовый файл с книгой можете скачать здесь(‘ alice ‘) или загрузить любое другое произведение. Напишем простую программу, которая подсчитает количество слов в книге и сколько раз повторяется имя Алиса в книге.

filename = ‘alice.txt’

with open(filename, encoding=’utf-8′) as file:
    contents = file.read()
n_alice = contents.lower().count(‘алиса’)
words = contents.split()
n_words = len(words)

print(f»Книга ‘Алиса в стране чудес’ содержит {n_words} слов.»)
print(f»Имя Алиса повторяется {n_alice} раз.»)

При открытии файла добавился аргумент encoding=’utf-8′. Он необходим, когда кодировка вашей системы не совпадает с кодировкой читаемого файла. После чтения файла, сохраним его в переменной contents.

Для подсчета вхождения слова или выражений в строке можно воспользоваться методом count(), но прежде привести все слова к нижнему регистру функцией lower(). Количество вхождений сохраним в переменной n_alice

Чтобы подсчитать количество слов в тексе, воспользуемся методом split(), предназначенный для построения списка слов на основе строки. Метод split() разделяет строку на части, где обнаружит пробел и сохраняет все части строки в элементах списка. Пример метода split():

title = ‘Алиса в стране чудес’
print(title.split())

[‘Алиса’, ‘в’, ‘стране’, ‘чудес’]

После использования метода split(), сохраним список в переменной words и далее подсчитаем количество слов в списке, с помощью функции len(). После подсчета всех данных, выведем на экран результат:

Книга ‘Алиса в стране чудес’ содержит 28389 слов.
Имя Алиса повторяется 419 раз.

2.1. Запись в пустой файл в Python

Самый простой способ сохранения данных, это записать их в файл. Чтобы записать текс в файл, требуется вызвать open() со вторым аргументом, который сообщит Python что требуется записать файл. Пример программы записи простого сообщения в файл на Python:

filename = ‘memory.txt’

with open(filename, ‘w’) as file:
    file.write(«Язык программирования Python»)

Для начала определим название и тип будущего файла и сохраним в переменную filename. Затем при вызове функции open() передадим два аргумента. Первый аргумент содержит имя открываемого файла. Второй аргумент ‘ w ‘ сообщает Python, что файл должен быть открыт в режиме записи. Во второй строчке метод write() используется для записи строки в файл. Открыв файл ‘ memory.txt ‘ вы увидите в нем строку:

Язык программирования Python

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

Важно: Открывая файл в режиме записи ‘ w ‘, если файл уже существует, то Python уничтожит его данные перед возвращением объекта файла.

Файлы можно открывать в режимах:

  • чтение ‘ r ‘
  • запись ‘ w ‘
  • присоединение ‘ a ‘
  • режим как чтения, так и записи ‘ r+ ‘

2.2. Многострочная запись в файл на Python

При использовании функции write() символы новой строки не добавляются в записываемый файл:

filename = ‘memory.txt’

with open(filename, ‘w’) as file:
    file.write(«Язык программирования Python»)
    file.write(«Язык программирования Java»)
    file.write(«Язык программирования Perl»)

В результате открыв файл мы увидим что все строки склеились:

Язык программирования PythonЯзык программирования JavaЯзык программирования Perl

Для написания каждого сообщения с новой строки используйте символ новой строки n

filename = ‘memory.txt’

with open(filename, ‘w’) as file:
    file.write(«Язык программирования Pythonn«)
    file.write(«Язык программирования Javan«)
    file.write(«Язык программирования Perln«)

Результат будет выглядеть так:

Язык программирования Python
Язык программирования Java
Язык программирования Perl

2.3. Присоединение данных к файлу на Python 

Для добавления новых данных в файл, вместо того чтобы постоянно перезаписывать файл, откройте файл в режиме присоединения ‘ a ‘. Все новые строки добавятся в конец файла. Возьмем созданный файл из раздела 2.2 ‘memory.txt’. Добавим в него еще пару строк.

filename = ‘memory.txt’

with open(filename, ‘a’) as file:
    file.write(«Hello worldn»)
    file.write(«Полет на лунуn»)

В результате к нашему файлу добавятся две строки:

Язык программирования Python
Язык программирования Java
Язык программирования Perl
Hello world
Полет на луну

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

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

3.1. Блоки try-except на Python

Приведем пример простой ошибки деления на ноль:

print(7/0)

Traceback (most recent call last):
  File «example.py», line 1, in <module>
    print(7/0)
ZeroDivisionError: division by zero

Если в вашей программе возможно появление ошибки, то вы можете заранее написать блок try-except для обработки данного исключения. Приведем пример обработки ошибки ZeroDivisionError с помощью блока try-except:

try:
    print(7/0)
except ZeroDivisionError:
    print(«Деление на ноль запрещено»)

Команда print(7/0) помещена в блок try. Если код в блоке try выполняется успешно, то Python пропускает блок except.  Если же код в блоке try создал ошибку, то Python ищет блок except и запускает код в этом блоке. В нашем случае в блоке except выводится сообщение «Деление на ноль запрещено». При выполнение этого кода пользователь увидит понятное сообщение:

Деление на ноль запрещено

Если за кодом try-except следует другой код, то Python продолжит выполнение программы. 

3.2. Блок try-except-else на Python

Напишем простой калькулятор, который запрашивает данные у пользователя, а затем результат деления выводит на экран. Сразу заключим возможную ошибку деления на ноль  ZeroDivisionError и добавим блок else при успешном выполнение блока try.

while True:
    first_number = input(«Введите первое число: «)
    if first_number == ‘q’:
        break
    second_number = input(«Введите второе число: «)
    if second_number == ‘q’:
        break
    try:
        a = int(first_number) / int(second_number)
    except ZeroDivisionError:
        print(«Деление на ноль запрещено»)
    else:
        print(f»Частное двух чисел равно {a}»)

Программа запрашивает у пользователя первое число (first_number), затем второе (second_number). Если пользователь не ввел » q « для завершения работы программа продолжается. В блок try помещаем код, в котором возможно появление ошибки. В случае отсутствия ошибки деления, выполняется код else и Python выводит результат на экран. В случае ошибки ZeroDivisionError выполняется блок except и выводится сообщение о запрете деления на ноль, а программа продолжит свое выполнение. Запустив код получим такие результаты:

Введите первое число: 30
Введите второе число: 5
Частное двух чисел равно 6.0
Введите первое число: 7
Введите второе число: 0
Деление на ноль запрещено
Введите первое число:  q

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

3.3. Блок  try-except с текстовыми файлами на Python

Одна из стандартных проблем при работе с файлами, это отсутствие необходимого файла, или файл находится в другом месте и Python не может его найти. Попробуем прочитать не существующий файл:

filename = ‘alice_2.txt’

with open(filename, encoding=’utf-8′) as file:
    contents = file.read()

Так как такого файла не существует, Python выдает исключение:

Traceback (most recent call last):
  File «example.py», line 3, in <module>
    with open(filename, encoding=’utf-8′) as file:
FileNotFoundError: [Errno 2] No such file or directory: ‘alice_2.txt’

FileNotFoundError — это ошибка отсутствия запрашиваемого файла. С помощью блока try-except обработаем ее:

filename = ‘alice_2.txt’

try:
    with open(filename, encoding=’utf-8′) as file:
        contents = file.read()
except FileNotFoundError:
    print(f»Запрашиваемый файл {filename } не найден»)

В результате при отсутствии файла мы получим:

Запрашиваемый файл alice_2.txt не найден

3.4. Ошибки без уведомления пользователя

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

ilename = ‘alice_2.txt’

try:
    with open(filename, encoding=’utf-8′) as file:
        contents = file.read()
except FileNotFoundError:
    pass

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

Далее: Функции json. Сохранение данных Python

Назад: Классы в Python

In this article we will discuss error handling using Python With Statements Try/Except/Finally statements, show how to use these in combination, and compare how it works to try/catch code blocks in other languages.

What is error handling?

Error handling is when you put in some extra code to tell your script what to do when things don’t go totally to plan. perhaps you try to open a file that isn’t there. Or perhaps a user puts in unexpected input.

Without any error handling, your program or script will simply crash, throw an error, and quit running. It is important to at least put in a minimal amount of error handling to ensure that your script/program will run reliably.

Try/Catch Statements

In many languages, you use Try/Catch statements for error handling. In C# for example, you might write some code that looks like this:

Try{
string text = System.IO.File.ReadAllText(@"C:UsersPublicTestFolderWriteText.txt");

}
Catch(exception e){
console.writeline(e);
}

The above code will attempt to read the txt file. If it cannot read the file, it will throw an exception. The catch code block then catches that exception into variable e. And uses the console.writeline method to print the exception onto the console. That way you can see what happened.

If you didn’t put in the try/catch blocks, the exception would have still been shown on the screen, but the application would have crashed and you have had to re-launch it. Sometimes on your computer, you might get an error about an un-caught exception right before your program closes. Those are cases like this one where the system.io.file.readalltext method was not in a try block of code.

Python Try, Exception & Finally Statements

Python does not have try/catch blocks. Instead, python has try/exception blocks. They really do the same thing but have different names.

Let’s look at an example written in python:

try:
  f = open(“test.txt", 'r')
  data = f.read()
  print("Trying")
except:
  print("Fiddlesticks! Failed")
finally:
  print("Finally!")
  print("All Done")

In the above script, we start the try statement and we attempt to execute the code in that section. If it succeeds, it will print the text trying. It will then jump down to the finally section and print Finally! followed by “All Done” on the console.

If the above script is not able to open the test.txt file, it will throw an exception and print “FIddleSticks!” to the console, followed by Finally.

The next logical question about these statements are what are the roles of each section?

  • The Try code block is the code you really want to execute.
  • The exception code block is the bit of code that you want to execute in the event you get an error while executing the code in the try code block.
  • The Finally code block is code that you want to execute regardless of the outcome.

More helpful errors

You may find that simply writing fiddlesticks when you have an error is not all that helpful. You need to capture the error so you can write it to a log file, or maybe you want to display the error on the screen.

Lets try executing this code in our python script:

f = open(“test.txt", 'r')
data = f.read()
print(data)

The above script will read test.txt and print the contents to the console. Unfortunately, if test.txt does not exist, you will get an error like this one:IOError: [Errno 2] No such file or directory: ‘test.txt’

Notice the type of error is IOError. That is helpful because we can create an exception block of code specifically around IOErrors. Lets look at how we would write that code:

try:
  f = open(“test.txt", 'r')
  data = f.read()
  print(data)
Except IOError as e:
  Print(e)
except:
  print("Fiddlesticks! Failed")
finally:
  print("Finally!")
print("All Done")

The above code will attempt to run what is in the try block. If it failed with an IOError, it will run the code in the except block. In this case, it will print out the error saying something about how it could not open or close a file. It will then run the finally code block when everything is finished.

If we want to have different logic for different kinds of exceptions, we could keep adding similar code like the code below.  Notice we call out each type of exception. Then we have the option to have a different remediation step for each exception type.

try:
  f = open("test.txt", 'r')
    Try:
      data = f.read()
      print(data)
    except IOError as e:
      print(e)
    except ValueError as e:
      print(e)
    except EOFError as e:
      print(e)
    Except:
      print(“unknown error”)
    Finally:
      f.close()
except:
  print("Fiddlesticks! Failed")
finally:
  print("All Done")

In the case of our example above, we are doing the exact same logic for each exception, so we might as well consolidate the exception handling into a single exception line.  That would look like this:

try:
  f = open("test.txt", 'r')
  data = f.read()
  print(data)
except (IOError, ValueError, EOFError) as e:
  print(e)
except:
  print("Fiddlesticks! Failed")
finally:
  print("All Done")

In the above example, we will print out the exception if it matches IOError, Valueerror, or EOFError. If it does not match any of those, it will print out Fiddlesticks. Here are a few of the most common exceptions you may want to handle:

  • IOError – file cannot be opened
  • ImportError – cannot find the specified module
  • EOFError – When the input reaches the end of a file and no more data can be read
  • ValueError – function receives an argument that has the right type but an invalid value
  • KeyboardInterrupt – User hits the interrupt key (Ctrl+D or Ctrl+C)

Or if you want a more comprehensive list of Python Exceptions You check look here.

Creating Custom Exceptions

In the previous section, we. were focused on handling exceptions using the exceptions that are built-in to Python. However, as you are developing your application, you will most likely encounter situations where you want to handle exceptions a bit differently. This is when you would create your own custom exceptions.

To handle your own custom Exceptions, you have to create a class for each exception type. Then put in some code for what to do when that exception has occurred. The most basic of exception classes looks like this:

class MyError(Exception):
    pass

raise MyError("Test Exception!")

**Note the pass statement is there just for syntax reasons. You could put additional code there instead.

If you run the above code, you will see output that says Test Exception, like this:

Now that we know the basics to create our own custom exception. Let’s start with a new example. We have a basic math function that just adds numbers together and returns the sum:

def addnumbers(x,y):
    return x+y

print(addnumbers(3,2))

When we run the code above, the output is the number 5. Now let’s say that we want to throw an exception if someone passes in the number 3. We can create a custom exception that lets the user of our function know that we don’t allow the number 3 as an input. Then we can add some code to check if the number 3 was passed in, and raise an exception in that case.

class BadNumbersError(Exception):
    pass

def addnumbers(x,y):
    if x ==3:
        raise BadNumbersError("We don't like the number 3")
    return x+y

print(addnumbers(3,2))

In the code above, you can see we created a class called BadNumbrersError. Then in our function, we added a check. If x==3, then raise a BadNumbersError exception and pass in the text “We don’t like the number 3”.

Next, we call our function and pass in values of 3 and 2. WIthout this new exception, the output would be the number 5. But now that we have added in our exception when you run the code, you should see what our new custom exception looks like.

As you can see, we trigger our exception and present a message to the user that says that we don’t like the number 3 when calling this function.

Python With statements

With statements can be used with try/catch statements to reduce the amount of code you need to write for handling different kinds of errors.

With statements call the __Enter__ and __Exit__ functions that are part of a given class. An example of this is with the File open class.

To properly handle errors when opening files, you would need some code similar to this:

try:
  f = open(“test.txt", 'r')
    Try:
      data = f.read()
      print(data)
    except IOError as e:
      print(e)
    Except:
      print(“unknown error”)
    Finally:
      f.close()
except:
  print("Fiddlesticks! Failed")
finally:
  print("All Done")

Before we get into the with statements, lets examine this code a bit. In the previous sections, we wrote some code to catch exceptions when opening a file. But the other issue is what happens if we have an issue after the file is already open. We need to make sure we close the file. We could put that in the finally section at the very bottom. But that will throw an exception if the original file never successfully opened. The result is this big mess of nested try/except statements to hopefully catch all of the different scenarios you may encounter.

Lucky for us, the makers of Python came out with a With Statement. Like I said previously, a with statement has an __enter__ and an __exit__ function that it calls at the beginning and the end of the statement. This allows some of that code to be removed from the big try/except mess I demonstrated above. An example of a with statement can be seen below:

with open(“test.txt”,r) as f:
text=f.read()
Print(text)

The above text will still throw an exception if test.txt does not exist. However, we no longer have to remember to call f.close when we are all finished with the file. If we do have the file open and an error occurs, the with statement will handle closing out the file handles for me. What this means is we can use the with statement in conjunction with our try/except code blocks to give us cleaner looking code. Let’s look at another example:

try:
  with open(“test.txt", 'r’) as f:
    data = f.read()
    print(data)
Except IOError as e:
  Print(e)
except:
  print("Fiddlesticks! Failed")
finally:
  print("Finally!")
print("All Done")

Notice the above example looks a lot like our original example. It is the same number of lines, and it is still very readable. However, it gives us similar functionality to the second example with the nested try/except loops.

Summary

In today’s article we discussed What is Error handling, What is the role of Try/Catch code blocks. How to set up exceptions, how to create our own custom extensions, and what a with statement does for us.

Error handling is a very important part of writing good software. Software without proper error handling will be unstable, and may not give good output when invalid input is entered into it.

Возможно, вам также будет интересно:

  • Wisl 82 коды ошибок как расшифровывается
  • Witcher3 exe системная ошибка отсутствует vcomp110 dll
  • Wisl 103 ошибка f10
  • Witcher3 exe системная ошибка отсутствует msvcr120 dll
  • Witcher3 exe системная ошибка отсутствует msvcp120 dll

  • Понравилась статья? Поделить с друзьями:
    0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии