Python function error code

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

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

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

Прежде чем переходить к обсуждению того, почему обработка исключений так важна, и рассматривать встроенные в 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 их обрабатывает.

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

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Raising and Handling Python Exceptions

A Python program terminates as soon as it encounters an error. In Python, an error can be a syntax error or an exception. In this article, you will see what an exception is and how it differs from a syntax error. After that, you will learn about raising exceptions and making assertions. Then, you’ll finish with a demonstration of the try and except block.

An introduction to exceptions in Python

Exceptions versus Syntax Errors

Syntax errors occur when the parser detects an incorrect statement. Observe the following example:

>>> print( 0 / 0 ))
  File "<stdin>", line 1
    print( 0 / 0 ))
                  ^
SyntaxError: invalid syntax

The arrow indicates where the parser ran into the syntax error. In this example, there was one bracket too many. Remove it and run your code again:

>>> print( 0 / 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

This time, you ran into an exception error. This type of error occurs whenever syntactically correct Python code results in an error. The last line of the message indicated what type of exception error you ran into.

Instead of showing the message exception error, Python details what type of exception error was encountered. In this case, it was a ZeroDivisionError. Python comes with various built-in exceptions as well as the possibility to create self-defined exceptions.

Raising an Exception

We can use raise to throw an exception if a condition occurs. The statement can be complemented with a custom exception.

Illustration of  raise statement usage

If you want to throw an error when a certain condition occurs using raise, you could go about it like this:

x = 10
if x > 5:
    raise Exception('x should not exceed 5. The value of x was: {}'.format(x))

When you run this code, the output will be the following:

Traceback (most recent call last):
  File "<input>", line 4, in <module>
Exception: x should not exceed 5. The value of x was: 10

The program comes to a halt and displays our exception to screen, offering clues about what went wrong.

The AssertionError Exception

Instead of waiting for a program to crash midway, you can also start by making an assertion in Python. We assert that a certain condition is met. If this condition turns out to be True, then that is excellent! The program can continue. If the condition turns out to be False, you can have the program throw an AssertionError exception.

Python assert statement

Have a look at the following example, where it is asserted that the code will be executed on a Linux system:

import sys
assert ('linux' in sys.platform), "This code runs on Linux only."

If you run this code on a Linux machine, the assertion passes. If you were to run this code on a Windows machine, the outcome of the assertion would be False and the result would be the following:

Traceback (most recent call last):
  File "<input>", line 2, in <module>
AssertionError: This code runs on Linux only.

In this example, throwing an AssertionError exception is the last thing that the program will do. The program will come to halt and will not continue. What if that is not what you want?

The try and except Block: Handling Exceptions

The try and except block in Python is used to catch and handle exceptions. Python executes code following the try statement as a “normal” part of the program. The code that follows the except statement is the program’s response to any exceptions in the preceding try clause.

Diagram showing try and except statements

As you saw earlier, when syntactically correct code runs into an error, Python will throw an exception error. This exception error will crash the program if it is unhandled. The except clause determines how your program responds to exceptions.

The following function can help you understand the try and except block:

def linux_interaction():
    assert ('linux' in sys.platform), "Function can only run on Linux systems."
    print('Doing something.')

The linux_interaction() can only run on a Linux system. The assert in this function will throw an AssertionError exception if you call it on an operating system other then Linux.

You can give the function a try using the following code:

try:
    linux_interaction()
except:
    pass

The way you handled the error here is by handing out a pass. If you were to run this code on a Windows machine, you would get the following output:

You got nothing. The good thing here is that the program did not crash. But it would be nice to see if some type of exception occurred whenever you ran your code. To this end, you can change the pass into something that would generate an informative message, like so:

try:
    linux_interaction()
except:
    print('Linux function was not executed')

Execute this code on a Windows machine:

Linux function was not executed

When an exception occurs in a program running this function, the program will continue as well as inform you about the fact that the function call was not successful.

What you did not get to see was the type of error that was thrown as a result of the function call. In order to see exactly what went wrong, you would need to catch the error that the function threw.

The following code is an example where you capture the AssertionError and output that message to screen:

try:
    linux_interaction()
except AssertionError as error:
    print(error)
    print('The linux_interaction() function was not executed')

Running this function on a Windows machine outputs the following:

Function can only run on Linux systems.
The linux_interaction() function was not executed

The first message is the AssertionError, informing you that the function can only be executed on a Linux machine. The second message tells you which function was not executed.

In the previous example, you called a function that you wrote yourself. When you executed the function, you caught the AssertionError exception and printed it to screen.

Here’s another example where you open a file and use a built-in exception:

try:
    with open('file.log') as file:
        read_data = file.read()
except:
    print('Could not open file.log')

If file.log does not exist, this block of code will output the following:

This is an informative message, and our program will still continue to run. In the Python docs, you can see that there are a lot of built-in exceptions that you can use here. One exception described on that page is the following:

Exception FileNotFoundError

Raised when a file or directory is requested but doesn’t exist. Corresponds to errno ENOENT.

To catch this type of exception and print it to screen, you could use the following code:

try:
    with open('file.log') as file:
        read_data = file.read()
except FileNotFoundError as fnf_error:
    print(fnf_error)

In this case, if file.log does not exist, the output will be the following:

[Errno 2] No such file or directory: 'file.log'

You can have more than one function call in your try clause and anticipate catching various exceptions. A thing to note here is that the code in the try clause will stop as soon as an exception is encountered.

Look at the following code. Here, you first call the linux_interaction() function and then try to open a file:

try:
    linux_interaction()
    with open('file.log') as file:
        read_data = file.read()
except FileNotFoundError as fnf_error:
    print(fnf_error)
except AssertionError as error:
    print(error)
    print('Linux linux_interaction() function was not executed')

If the file does not exist, running this code on a Windows machine will output the following:

Function can only run on Linux systems.
Linux linux_interaction() function was not executed

Inside the try clause, you ran into an exception immediately and did not get to the part where you attempt to open file.log. Now look at what happens when you run the code on a Linux machine:

[Errno 2] No such file or directory: 'file.log'

Here are the key takeaways:

  • A try clause is executed up until the point where the first exception is encountered.
  • Inside the except clause, or the exception handler, you determine how the program responds to the exception.
  • You can anticipate multiple exceptions and differentiate how the program should respond to them.
  • Avoid using bare except clauses.

The else Clause

In Python, using the else statement, you can instruct a program to execute a certain block of code only in the absence of exceptions.

Diagram of try, except, and else statements in Python

Look at the following example:

try:
    linux_interaction()
except AssertionError as error:
    print(error)
else:
    print('Executing the else clause.')

If you were to run this code on a Linux system, the output would be the following:

Doing something.
Executing the else clause.

Because the program did not run into any exceptions, the else clause was executed.

You can also try to run code inside the else clause and catch possible exceptions there as well:

try:
    linux_interaction()
except AssertionError as error:
    print(error)
else:
    try:
        with open('file.log') as file:
            read_data = file.read()
    except FileNotFoundError as fnf_error:
        print(fnf_error)

If you were to execute this code on a Linux machine, you would get the following result:

Doing something.
[Errno 2] No such file or directory: 'file.log'

From the output, you can see that the linux_interaction() function ran. Because no exceptions were encountered, an attempt to open file.log was made. That file did not exist, and instead of opening the file, you caught the FileNotFoundError exception.

Cleaning Up After Using finally

Imagine that you always had to implement some sort of action to clean up after executing your code. Python enables you to do so using the finally clause.

Diagram explaining try except else finally statements

Have a look at the following example:

try:
    linux_interaction()
except AssertionError as error:
    print(error)
else:
    try:
        with open('file.log') as file:
            read_data = file.read()
    except FileNotFoundError as fnf_error:
        print(fnf_error)
finally:
    print('Cleaning up, irrespective of any exceptions.')

In the previous code, everything in the finally clause will be executed. It does not matter if you encounter an exception somewhere in the try or else clauses. Running the previous code on a Windows machine would output the following:

Function can only run on Linux systems.
Cleaning up, irrespective of any exceptions.

Summing Up

After seeing the difference between syntax errors and exceptions, you learned about various ways to raise, catch, and handle exceptions in Python. In this article, you saw the following options:

  • raise allows you to throw an exception at any time.
  • assert enables you to verify if a certain condition is met and throw an exception if it isn’t.
  • In the try clause, all statements are executed until an exception is encountered.
  • except is used to catch and handle the exception(s) that are encountered in the try clause.
  • else lets you code sections that should run only when no exceptions are encountered in the try clause.
  • finally enables you to execute sections of code that should always run, with or without any previously encountered exceptions.

Hopefully, this article helped you understand the basic tools that Python has to offer when dealing with exceptions.

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Raising and Handling Python Exceptions

Errors¶

Errors or mistakes in a program are often referred to as bugs. They are almost always the fault of the programmer. The process of finding and eliminating errors is called debugging. Errors can be classified into three major groups:

  • Syntax errors
  • Runtime errors
  • Logical errors

Syntax errors¶

Python will find these kinds of errors when it tries to parse your program, and exit with an error message without running anything. Syntax errors are mistakes in the use of the Python language, and are analogous to spelling or grammar mistakes in a language like English: for example, the sentence Would you some tea? does not make sense – it is missing a verb.

Common Python syntax errors include:

  • leaving out a keyword
  • putting a keyword in the wrong place
  • leaving out a symbol, such as a colon, comma or brackets
  • misspelling a keyword
  • incorrect indentation
  • empty block

Note

it is illegal for any block (like an if body, or the body of a function) to be left completely empty. If you want a block to do nothing, you can use the pass statement inside the block.

Python will do its best to tell you where the error is located, but sometimes its messages can be misleading: for example, if you forget to escape a quotation mark inside a string you may get a syntax error referring to a place later in your code, even though that is not the real source of the problem. If you can’t see anything wrong on the line specified in the error message, try backtracking through the previous few lines. As you program more, you will get better at identifying and fixing errors.

Here are some examples of syntax errors in Python:

myfunction(x, y):
    return x + y

else:
    print("Hello!")

if mark >= 50
    print("You passed!")

if arriving:
    print("Hi!")
esle:
    print("Bye!")

if flag:
print("Flag is set!")

Runtime errors¶

If a program is syntactically correct – that is, free of syntax errors – it will be run by the Python interpreter. However, the program may exit unexpectedly during execution if it encounters a runtime error – a problem which was not detected when the program was parsed, but is only revealed when a particular line is executed. When a program comes to a halt because of a runtime error, we say that it has crashed.

Consider the English instruction flap your arms and fly to Australia. While the instruction is structurally correct and you can understand its meaning perfectly, it is impossible for you to follow it.

Some examples of Python runtime errors:

  • division by zero
  • performing an operation on incompatible types
  • using an identifier which has not been defined
  • accessing a list element, dictionary value or object attribute which doesn’t exist
  • trying to access a file which doesn’t exist

Runtime errors often creep in if you don’t consider all possible values that a variable could contain, especially when you are processing user input. You should always try to add checks to your code to make sure that it can deal with bad input and edge cases gracefully. We will look at this in more detail in the chapter about exception handling.

Logical errors¶

Logical errors are the most difficult to fix. They occur when the program runs without crashing, but produces an incorrect result. The error is caused by a mistake in the program’s logic. You won’t get an error message, because no syntax or runtime error has occurred. You will have to find the problem on your own by reviewing all the relevant parts of your code – although some tools can flag suspicious code which looks like it could cause unexpected behaviour.

Sometimes there can be absolutely nothing wrong with your Python implementation of an algorithm – the algorithm itself can be incorrect. However, more frequently these kinds of errors are caused by programmer carelessness. Here are some examples of mistakes which lead to logical errors:

  • using the wrong variable name
  • indenting a block to the wrong level
  • using integer division instead of floating-point division
  • getting operator precedence wrong
  • making a mistake in a boolean expression
  • off-by-one, and other numerical errors

If you misspell an identifier name, you may get a runtime error or a logical error, depending on whether the misspelled name is defined.

A common source of variable name mix-ups and incorrect indentation is frequent copying and pasting of large blocks of code. If you have many duplicate lines with minor differences, it’s very easy to miss a necessary change when you are editing your pasted lines. You should always try to factor out excessive duplication using functions and loops – we will look at this in more detail later.

Exercise 1¶

  1. Find all the syntax errors in the code snippet above, and explain why they are errors.

  2. Find potential sources of runtime errors in this code snippet:

    dividend = float(input("Please enter the dividend: "))
    divisor = float(input("Please enter the divisor: "))
    quotient = dividend / divisor
    quotient_rounded = math.round(quotient)
    
  3. Find potential sources of runtime errors in this code snippet:

    for x in range(a, b):
        print("(%f, %f, %f)" % my_list[x])
    
  4. Find potential sources of logic errors in this code snippet:

    product = 0
    for i in range(10):
        product *= i
    
    sum_squares = 0
    for i in range(10):
        i_sq = i**2
    sum_squares += i_sq
    
    nums = 0
    for num in range(10):
        num += num
    

Handling exceptions¶

Until now, the programs that we have written have generally ignored the fact that things can go wrong. We have have tried to prevent runtime errors by checking data which may be incorrect before we used it, but we haven’t yet seen how we can handle errors when they do occur – our programs so far have just crashed suddenly whenever they have encountered one.

There are some situations in which runtime errors are likely to occur. Whenever we try to read a file or get input from a user, there is a chance that something unexpected will happen – the file may have been moved or deleted, and the user may enter data which is not in the right format. Good programmers should add safeguards to their programs so that common situations like this can be handled gracefully – a program which crashes whenever it encounters an easily foreseeable problem is not very pleasant to use. Most users expect programs to be robust enough to recover from these kinds of setbacks.

If we know that a particular section of our program is likely to cause an error, we can tell Python what to do if it does happen. Instead of letting the error crash our program we can intercept it, do something about it, and allow the program to continue.

All the runtime (and syntax) errors that we have encountered are called exceptions in Python – Python uses them to indicate that something exceptional has occurred, and that your program cannot continue unless it is handled. All exceptions are subclasses of the Exception class – we will learn more about classes, and how to write your own exception types, in later chapters.

The try and except statements¶

To handle possible exceptions, we use a try-except block:

try:
    age = int(input("Please enter your age: "))
    print("I see that you are %d years old." % age)
except ValueError:
    print("Hey, that wasn't a number!")

Python will try to process all the statements inside the try block. If a ValueError occurs at any point as it is executing them, the flow of control will immediately pass to the except block, and any remaining statements in the try block will be skipped.

In this example, we know that the error is likely to occur when we try to convert the user’s input to an integer. If the input string is not a number, this line will trigger a ValueError – that is why we specified it as the type of error that we are going to handle.

We could have specified a more general type of error – or even left the type out entirely, which would have caused the except clause to match any kind of exception – but that would have been a bad idea. What if we got a completely different error that we hadn’t predicted? It would be handled as well, and we wouldn’t even notice that anything unusual was going wrong. We may also want to react in different ways to different kinds of errors. We should always try pick specific rather than general error types for our except clauses.

It is possible for one except clause to handle more than one kind of error: we can provide a tuple of exception types instead of a single type:

try:
    dividend = int(input("Please enter the dividend: "))
    divisor = int(input("Please enter the divisor: "))
    print("%d / %d = %f" % (dividend, divisor, dividend/divisor))
except(ValueError, ZeroDivisionError):
    print("Oops, something went wrong!")

A try-except block can also have multiple except clauses. If an exception occurs, Python will check each except clause from the top down to see if the exception type matches. If none of the except clauses match, the exception will be considered unhandled, and your program will crash:

try:
    dividend = int(input("Please enter the dividend: "))
    divisor = int(input("Please enter the divisor: "))
    print("%d / %d = %f" % (dividend, divisor, dividend/divisor))
except ValueError:
    print("The divisor and dividend have to be numbers!")
except ZeroDivisionError:
    print("The dividend may not be zero!")

Note that in the example above if a ValueError occurs we won’t know whether it was caused by the dividend or the divisor not being an integer – either one of the input lines could cause that error. If we want to give the user more specific feedback about which input was wrong, we will have to wrap each input line in a separate try-except block:

try:
    dividend = int(input("Please enter the dividend: "))
except ValueError:
    print("The dividend has to be a number!")

try:
    divisor = int(input("Please enter the divisor: "))
except ValueError:
    print("The divisor has to be a number!")

try:
    print("%d / %d = %f" % (dividend, divisor, dividend/divisor))
except ZeroDivisionError:
    print("The dividend may not be zero!")

In general, it is a better idea to use exception handlers to protect small blocks of code against specific errors than to wrap large blocks of code and write vague, generic error recovery code. It may sometimes seem inefficient and verbose to write many small try-except statements instead of a single catch-all statement, but we can mitigate this to some extent by making effective use of loops and functions to reduce the amount of code duplication.

How an exception is handled¶

When an exception occurs, the normal flow of execution is interrupted. Python checks to see if the line of code which caused the exception is inside a try block. If it is, it checks to see if any of the except blocks associated with the try block can handle that type of exception. If an appropriate handler is found, the exception is handled, and the program continues from the next statement after the end of that try-except.

If there is no such handler, or if the line of code was not in a try block, Python will go up one level of scope: if the line of code which caused the exception was inside a function, that function will exit immediately, and the line which called the function will be treated as if it had thrown the exception. Python will check if that line is inside a try block, and so on. When a function is called, it is placed on Python’s stack, which we will discuss in the chapter about functions. Python traverses this stack when it tries to handle an exception.

If an exception is thrown by a line which is in the main body of your program, not inside a function, the program will terminate. When the exception message is printed, you should also see a traceback – a list which shows the path the exception has taken, all the way back to the original line which caused the error.

Error checks vs exception handling¶

Exception handling gives us an alternative way to deal with error-prone situations in our code. Instead of performing more checks before we do something to make sure that an error will not occur, we just try to do it – and if an error does occur we handle it. This can allow us to write simpler and more readable code. Let’s look at a more complicated input example – one in which we want to keep asking the user for input until the input is correct. We will try to write this example using the two different approaches:

# with checks

n = None
while n is None:
    s = input("Please enter an integer: ")
    if s.lstrip('-').isdigit():
        n = int(s)
    else:
        print("%s is not an integer." % s)

# with exception handling

n = None
while n is None:
    try:
        s = input("Please enter an integer: ")
        n = int(s)
    except ValueError:
        print("%s is not an integer." % s)

In the first code snippet, we have to write quite a convoluted check to test whether the user’s input is an integer – first we strip off a minus sign if it exists, and then we check if the rest of the string consists only of digits. But there’s a very simple criterion which is also what we really want to know: will this string cause a ValueError if we try to convert it to an integer? In the second snippet we can in effect check for exactly the right condition instead of trying to replicate it ourselves – something which isn’t always easy to do. For example, we could easily have forgotten that integers can be negative, and written the check in the first snippet incorrectly.

Here are a few other advantages of exception handling:

  • It separates normal code from code that handles errors.
  • Exceptions can easily be passed along functions in the stack until they reach a function which knows how to handle them. The intermediate functions don’t need to have any error-handling code.
  • Exceptions come with lots of useful error information built in – for example, they can print a traceback which helps us to see exactly where the error occurred.

The else and finally statements¶

There are two other clauses that we can add to a try-except block: else and finally. else will be executed only if the try clause doesn’t raise an exception:

try:
    age = int(input("Please enter your age: "))
except ValueError:
    print("Hey, that wasn't a number!")
else:
    print("I see that you are %d years old." % age)

We want to print a message about the user’s age only if the integer conversion succeeds. In the first exception handler example, we put this print statement directly after the conversion inside the try block. In both cases, the statement will only be executed if the conversion statement doesn’t raise an exception, but putting it in the else block is better practice – it means that the only code inside the try block is the single line that is the potential source of the error that we want to handle.

When we edit this program in the future, we may introduce additional statements that should also be executed if the age input is successfully converted. Some of these statements may also potentially raise a ValueError. If we don’t notice this, and put them inside the try clause, the except clause will also handle these errors if they occur. This is likely to cause some odd and unexpected behaviour. By putting all this extra code in the else clause instead, we avoid taking this risk.

The finally clause will be executed at the end of the try-except block no matter what – if there is no exception, if an exception is raised and handled, if an exception is raised and not handled, and even if we exit the block using break, continue or return. We can use the finally clause for cleanup code that we always want to be executed:

try:
    age = int(input("Please enter your age: "))
except ValueError:
    print("Hey, that wasn't a number!")
else:
    print("I see that you are %d years old." % age)
finally:
    print("It was really nice talking to you.  Goodbye!")

Exercise 2¶

  1. Extend the program in exercise 7 of the loop control statements chapter to include exception handling. Whenever the user enters input of the incorrect type, keep prompting the user for the same value until it is entered correctly. Give the user sensible feedback.

  2. Add a try-except statement to the body of this function which handles a possible IndexError, which could occur if the index provided exceeds the length of the list. Print an error message if this happens:

    def print_list_element(thelist, index):
        print(thelist[index])
    
  3. This function adds an element to a list inside a dict of lists. Rewrite it to use a try-except statement which handles a possible KeyError if the list with the name provided doesn’t exist in the dictionary yet, instead of checking beforehand whether it does. Include else and finally clauses in your try-except block:

    def add_to_list_in_dict(thedict, listname, element):
        if listname in thedict:
            l = thedict[listname]
            print("%s already has %d elements." % (listname, len(l)))
        else:
            thedict[listname] = []
            print("Created %s." % listname)
    
        thedict[listname].append(element)
    
        print("Added %s to %s." % (element, listname))
    

The with statement¶

Using the exception object¶

Python’s exception objects contain more information than just the error type. They also come with some kind of message – we have already seen some of these messages displayed when our programs have crashed. Often these messages aren’t very user-friendly – if we want to report an error to the user we usually need to write a more descriptive message which explains how the error is related to what the user did. For example, if the error was caused by incorrect input, it is helpful to tell the user which of the input values was incorrect.

Sometimes the exception message contains useful information which we want to display to the user. In order to access the message, we need to be able to access the exception object. We can assign the object to a variable that we can use inside the except clause like this:

try:
    age = int(input("Please enter your age: "))
except ValueError as err:
    print(err)

err is not a string, but Python knows how to convert it into one – the string representation of an exception is the message, which is exactly what we want. We can also combine the exception message with our own message:

try:
    age = int(input("Please enter your age: "))
except ValueError as err:
    print("You entered incorrect age input: %s" % err)

Note that inserting a variable into a formatted string using %s also converts the variable to a string.

Raising exceptions¶

We can raise exceptions ourselves using the raise statement:

try:
    age = int(input("Please enter your age: "))
    if age < 0:
        raise ValueError("%d is not a valid age. Age must be positive or zero.")
except ValueError as err:
    print("You entered incorrect age input: %s" % err)
else:
    print("I see that you are %d years old." % age)

We can raise our own ValueError if the age input is a valid integer, but it’s negative. When we do this, it has exactly the same effect as any other exception – the flow of control will immediately exit the try clause at this point and pass to the except clause. This except clause can match our exception as well, since it is also a ValueError.

We picked ValueError as our exception type because it’s the most appropriate for this kind of error. There’s nothing stopping us from using a completely inappropriate exception class here, but we should try to be consistent. Here are a few common exception types which we are likely to raise in our own code:

  • TypeError: this is an error which indicates that a variable has the wrong type for some operation. We might raise it in a function if a parameter is not of a type that we know how to handle.
  • ValueError: this error is used to indicate that a variable has the right type but the wrong value. For example, we used it when age was an integer, but the wrong kind of integer.
  • NotImplementedError: we will see in the next chapter how we use this exception to indicate that a class’s method has to be implemented in a child class.

We can also write our own custom exception classes which are based on existing exception classes – we will see some examples of this in a later chapter.

Something we may want to do is raise an exception that we have just intercepted – perhaps because we want to handle it partially in the current function, but also want to respond to it in the code which called the function:

try:
    age = int(input("Please enter your age: "))
except ValueError as err:
    print("You entered incorrect age input: %s" % err)
    raise err

Exercise 3¶

  1. Rewrite the program from the first question of exercise 2 so that it prints the text of Python’s original exception inside the except clause instead of a custom message.
  2. Rewrite the program from the second question of exercise 2 so that the exception which is caught in the except clause is re-raised after the error message is printed.

Debugging programs¶

Syntax errors are usually quite straightforward to debug: the error message shows us the line in the file where the error is, and it should be easy to find it and fix it.

Runtime errors can be a little more difficult to debug: the error message and the traceback can tell us exactly where the error occurred, but that doesn’t necessarily tell us what the problem is. Sometimes they are caused by something obvious, like an incorrect identifier name, but sometimes they are triggered by a particular state of the program – it’s not always clear which of many variables has an unexpected value.

Logical errors are the most difficult to fix because they don’t cause any errors that can be traced to a particular line in the code. All that we know is that the code is not behaving as it should be – sometimes tracking down the area of the code which is causing the incorrect behaviour can take a long time.

It is important to test your code to make sure that it behaves the way that you expect. A quick and simple way of testing that a function is doing the right thing, for example, is to insert a print statement after every line which outputs the intermediate results which were calculated on that line. Most programmers intuitively do this as they are writing a function, or perhaps if they need to figure out why it isn’t doing the right thing:

def hypotenuse(x, y):
    print("x is %f and y is %f" % (x, y))
    x_2 = x**2
    print(x_2)
    y_2 = y**2
    print(y_2)
    z_2 = x_2 + y_2
    print(z_2)
    z = math.sqrt(z_2)
    print(z)
    return z

This is a quick and easy thing to do, and even experienced programmers are guilty of doing it every now and then, but this approach has several disadvantages:

  • As soon as the function is working, we are likely to delete all the print statements, because we don’t want our program to print all this debugging information all the time. The problem is that code often changes – the next time we want to test this function we will have to add the print statements all over again.

  • To avoid rewriting the print statements if we happen to need them again, we may be tempted to comment them out instead of deleting them – leaving them to clutter up our code, and possibly become so out of sync that they end up being completely useless anyway.

  • To print out all these intermediate values, we had to spread out the formula inside the function over many lines. Sometimes it is useful to break up a calculation into several steps, if it is very long and putting it all on one line makes it hard to read, but sometimes it just makes our code unnecessarily verbose. Here is what the function above would normally look like:

    def hypotenuse(x, y):
        return math.sqrt(x**2 + y**2)
    

How can we do this better? If we want to inspect the values of variables at various steps of a program’s execution, we can use a tool like pdb. If we want our program to print out informative messages, possibly to a file, and we want to be able to control the level of detail at runtime without having to change anything in the code, we can use logging.

Most importantly, to check that our code is working correctly now and will keep working correctly, we should write a permanent suite of tests which we can run on our code regularly. We will discuss testing in more detail in a later chapter.

Logging¶

Sometimes it is valuable for a program to output messages to a console or a file as it runs. These messages can be used as a record of the program’s execution, and help us to find errors. Sometimes a bug occurs intermittently, and we don’t know what triggers it – if we only add debugging output to our program when we want to begin an active search for the bug, we may be unable to reproduce it. If our program logs messages to a file all the time, however, we may find that some helpful information has been recorded when we check the log after the bug has occurred.

Some kinds of messages are more important than others – errors are noteworthy events which should almost always be logged. Messages which record that an operation has been completed successfully may sometimes be useful, but are not as important as errors. Detailed messages which debug every step of a calculation can be interesting if we are trying to debug the calculation, but if they were printed all the time they would fill the console with noise (or make our log file really, really big).

We can use Python’s logging module to add logging to our program in an easy and consistent way. Logging statements are almost like print statements, but whenever we log a message we specify a level for the message. When we run our program, we set a desired log level for the program. Only messages which have a level greater than or equal to the level which we have set will appear in the log. This means that we can temporarily switch on detailed logging and switch it off again just by changing the log level in one place.

There is a consistent set of logging level names which most languages use. In order, from the highest value (most severe) to the lowest value (least severe), they are:

  • CRITICAL – for very serious errors
  • ERROR – for less serious errors
  • WARNING – for warnings
  • INFO – for important informative messages
  • DEBUG – for detailed debugging messages

These names are used for integer constants defined in the logging module. The module also provides methods which we can use to log messages. By default these messages are printed to the console, and the default log level is WARNING. We can configure the module to customise its behaviour – for example, we can write the messages to a file instead, raise or lower the log level and change the message format. Here is a simple logging example:

import logging

# log messages to a file, ignoring anything less severe than ERROR
logging.basicConfig(filename='myprogram.log', level=logging.ERROR)

# these messages should appear in our file
logging.error("The washing machine is leaking!")
logging.critical("The house is on fire!")

# but these ones won't
logging.warning("We're almost out of milk.")
logging.info("It's sunny today.")
logging.debug("I had eggs for breakfast.")

There’s also a special exception method which is used for logging exceptions. The level used for these messages is ERROR, but additional information about the exception is added to them. This method is intended to be used inside exception handlers instead of error:

try:
    age = int(input("How old are you? "))
except ValueError as err:
    logging.exception(err)

If we have a large project, we may want to set up a more complicated system for logging – perhaps we want to format certain messages differently, log different messages to different files, or log to multiple locations at the same time. The logging module also provides us with logger and handler objects for this purpose. We can use multiple loggers to create our messages, customising each one independently. Different handlers are associated with different logging locations. We can connect up our loggers and handlers in any way we like – one logger can use many handlers, and multiple loggers can use the same handler.

Exercise 4¶

  1. Write logging configuration for a program which logs to a file called log.txt and discards all logs less important than INFO.
  2. Rewrite the second program from exercise 2 so that it uses this logging configuration instead of printing messages to the console (except for the first print statement, which is the purpose of the function).
  3. Do the same with the third program from exercise 2.

Answers to exercises¶

Answer to exercise 1¶

  1. There are five syntax errors:

    1. Missing def keyword in function definition
    2. else clause without an if
    3. Missing colon after if condition
    4. Spelling mistake (“esle”)
    5. The if block is empty because the print statement is not indented correctly
    1. The values entered by the user may not be valid integers or floating-point numbers.
    2. The user may enter zero for the divisor.
    3. If the math library hasn’t been imported, math.round is undefined.
    1. a, b and my_list need to be defined before this snippet.
    2. The attempt to access the list element with index x may fail during one of the loop iterations if the range from a to b exceeds the size of my_list.
    3. The string formatting operation inside the print statement expects my_list[x] to be a tuple with three numbers. If it has too many or too few elements, or isn’t a tuple at all, the attempt to format the string will fail.
    1. If you are accumulating a number total by multiplication, not addition, you need to initialise the total to 1, not 0, otherwise the product will always be zero!
    2. The line which adds i_sq to sum_squares is not aligned correctly, and will only add the last value of i_sq after the loop has concluded.
    3. The wrong variable is used: at each loop iteration the current number in the range is added to itself and nums remains unchanged.

Answer to exercise 2¶

  1. Here is an example program:

    person = {}
    
    properties = [
        ("name", str),
        ("surname", str),
        ("age", int),
        ("height", float),
        ("weight", float),
    ]
    
    for property, p_type in properties:
        valid_value = None
    
        while valid_value is None:
            try:
                value = input("Please enter your %s: " % property)
                valid_value = p_type(value)
            except ValueError:
                print("Could not convert %s '%s' to type %s. Please try again." % (property, value, p_type.__name__))
    
        person[property] = valid_value
    
  2. Here is an example program:

    def print_list_element(thelist, index):
        try:
            print(thelist[index])
        except IndexError:
            print("The list has no element at index %d." % index)
    
  3. Here is an example program:

    def add_to_list_in_dict(thedict, listname, element):
        try:
            l = thedict[listname]
        except KeyError:
            thedict[listname] = []
            print("Created %s." % listname)
        else:
            print("%s already has %d elements." % (listname, len(l)))
        finally:
            thedict[listname].append(element)
            print("Added %s to %s." % (element, listname))
    

Answer to exercise 3¶

  1. Here is an example program:

    person = {}
    
    properties = [
        ("name", str),
        ("surname", str),
        ("age", int),
        ("height", float),
        ("weight", float),
    ]
    
    for property, p_type in properties:
        valid_value = None
    
        while valid_value is None:
            try:
                value = input("Please enter your %s: " % property)
                valid_value = p_type(value)
            except ValueError as ve:
                print(ve)
    
        person[property] = valid_value
    
  2. Here is an example program:

    def print_list_element(thelist, index):
        try:
            print(thelist[index])
        except IndexError as ie:
            print("The list has no element at index %d." % index)
            raise ie
    

Answer to exercise 4¶

  1. Here is an example of the logging configuration:

    import logging
    logging.basicConfig(filename='log.txt', level=logging.INFO)
    
  2. Here is an example program:

    def print_list_element(thelist, index):
        try:
            print(thelist[index])
        except IndexError:
            logging.error("The list has no element at index %d." % index)
    
  3. Here is an example program:

    def add_to_list_in_dict(thedict, listname, element):
        try:
            l = thedict[listname]
        except KeyError:
            thedict[listname] = []
            logging.info("Created %s." % listname)
        else:
            logging.info("%s already has %d elements." % (listname, len(l)))
        finally:
            thedict[listname].append(element)
            logging.info("Added %s to %s." % (element, listname))
    

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

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

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

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

ZeroDivisionError: division by zero

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

и

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


class EvenError(NumbersError):
    pass


class NegativeError(NumbersError):
    pass


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


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


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

        
if __name__ == "__main__":
    main()

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

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

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

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

import example_module

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

from example_module import some_function, ExampleClass

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

Improve Article

Save Article

  • Read
  • Discuss
  • Improve Article

    Save Article

    Errors are the problems in a program due to which the program will stop the execution. On the other hand, exceptions are raised when some internal events occur which changes the normal flow of the program. 
    Two types of Error occurs in python. 
     

    1. Syntax errors
    2. Logical errors (Exceptions) 
       

    Syntax errors

    When the proper syntax of the language is not followed then a syntax error is thrown.
    Example 
     

    Python3

    amount = 10000

    if(amount>2999)

        print("You are eligible to purchase Dsa Self Paced")

    Output:
     

    It returns a syntax error message because after the if statement a colon: is missing. We can fix this by writing the correct syntax.
     

    logical errors(Exception)

    When in the runtime an error that occurs after passing the syntax test is called exception or logical type. For example, when we divide any number by zero then the ZeroDivisionError exception is raised, or when we import a module that does not exist then ImportError is raised.
    Example 1: 
     

    Python3

    marks = 10000

    a = marks / 0

    print(a)

    Output:
     

    In the above example the ZeroDivisionError as we are trying to divide a number by 0.
    Example 2: When indentation is not correct. 
     

    Python3

    Output:
     

    Some of the common built-in exceptions are other than above mention exceptions are:
     

    Exception Description
    IndexError When the wrong index of a list is retrieved.
    AssertionError It occurs when the assert statement fails
    AttributeError It occurs when an attribute assignment is failed.
    ImportError It occurs when an imported module is not found.
    KeyError It occurs when the key of the dictionary is not found.
    NameError It occurs when the variable is not defined.
    MemoryError It occurs when a program runs out of memory.
    TypeError It occurs when a function and operation are applied in an incorrect type.

    Note: For more information, refer to Built-in Exceptions in Python
     

    Error Handling

    When an error and an exception are raised then we handle that with the help of the Handling method.
     

    • Handling Exceptions with Try/Except/Finally 
      We can handle errors by the Try/Except/Finally method. we write unsafe code in the try, fall back code in except and final code in finally block.
      Example 
       

    Python3

    try:

         print("code start")

         print(1 / 0)

    except:

         print("an error occurs")

    finally:

         print("GeeksForGeeks")

    • Output: 
       
    code start
    an error occurs
    GeeksForGeeks
    •  
    • Raising exceptions for a predefined condition 
      When we want to code for the limitation of certain conditions then we can raise an exception. 
      Example 
       

    Python3

    try:

        amount = 1999

        if amount < 2999:

            raise ValueError("please add money in your account")

        else:

            print("You are eligible to purchase DSA Self Paced course")

    except ValueError as e:

            print(e)

    • Output: 
       
    please add money in your account

    Overview

    Teaching: 30 min

    Exercises: 0 min

    Questions

    • How does Python report errors?

    • How can I handle errors in Python programs?

    Objectives

    • To be able to read a traceback, and determine where the error took place and what type it is.

    • To be able to describe the types of situations in which syntax errors, indentation errors, name errors, index errors, and missing file errors occur.

    Every programmer encounters errors,
    both those who are just beginning,
    and those who have been programming for years.
    Encountering errors and exceptions can be very frustrating at times,
    and can make coding feel like a hopeless endeavour.
    However,
    understanding what the different types of errors are
    and when you are likely to encounter them can help a lot.
    Once you know why you get certain types of errors,
    they become much easier to fix.

    Errors in Python have a very specific form,
    called a traceback.
    Let’s examine one:

    # This code has an intentional error. You can type it directly or
    # use it for reference to understand the error message below.
    def favorite_ice_cream():
        ice_creams = [
            'chocolate',
            'vanilla',
            'strawberry'
        ]
        print(ice_creams[3])
    
    favorite_ice_cream()
    
    ---------------------------------------------------------------------------
    IndexError                                Traceback (most recent call last)
    <ipython-input-1-70bd89baa4df> in <module>()
          9     print(ice_creams[3])
          10
    ----> 11 favorite_ice_cream()
    
    <ipython-input-1-70bd89baa4df> in favorite_ice_cream()
          7         'strawberry'
          8     ]
    ----> 9     print(ice_creams[3])
          10
          11 favorite_ice_cream()
    
    IndexError: list index out of range
    

    This particular traceback has two levels.
    You can determine the number of levels by looking for the number of arrows on the left hand side.
    In this case:

    1. The first shows code from the cell above,
      with an arrow pointing to Line 11 (which is favorite_ice_cream()).

    2. The second shows some code in the function favorite_ice_cream,
      with an arrow pointing to Line 9 (which is print(ice_creams[3])).

    The last level is the actual place where the error occurred.
    The other level(s) show what function the program executed to get to the next level down.
    So, in this case, the program first performed a
    function call to the function favorite_ice_cream.
    Inside this function,
    the program encountered an error on Line 6, when it tried to run the code print(ice_creams[3]).

    Long Tracebacks

    Sometimes, you might see a traceback that is very long
    – sometimes they might even be 20 levels deep!
    This can make it seem like something horrible happened,
    but the length of the error message does not reflect severity, rather,
    it indicates that your program called many functions before it encountered the error.
    Most of the time, the actual place where the error occurred is at the bottom-most level,
    so you can skip down the traceback to the bottom.

    So what error did the program actually encounter?
    In the last line of the traceback,
    Python helpfully tells us the category or type of error (in this case, it is an IndexError)
    and a more detailed error message (in this case, it says “list index out of range”).

    If you encounter an error and don’t know what it means,
    it is still important to read the traceback closely.
    That way,
    if you fix the error,
    but encounter a new one,
    you can tell that the error changed.
    Additionally,
    sometimes knowing where the error occurred is enough to fix it,
    even if you don’t entirely understand the message.

    If you do encounter an error you don’t recognize,
    try looking at the
    official documentation on errors.
    However,
    note that you may not always be able to find the error there,
    as it is possible to create custom errors.
    In that case,
    hopefully the custom error message is informative enough to help you figure out what went wrong.

    Syntax Errors

    When you forget a colon at the end of a line,
    accidentally add one space too many when indenting under an if statement,
    or forget a parenthesis,
    you will encounter a syntax error.
    This means that Python couldn’t figure out how to read your program.
    This is similar to forgetting punctuation in English:
    for example,
    this text is difficult to read there is no punctuation there is also no capitalization
    why is this hard because you have to figure out where each sentence ends
    you also have to figure out where each sentence begins
    to some extent it might be ambiguous if there should be a sentence break or not

    People can typically figure out what is meant by text with no punctuation,
    but people are much smarter than computers.
    If Python doesn’t know how to read the program,
    it will give up and inform you with an error.
    For example:

    def some_function()
        msg = 'hello, world!'
        print(msg)
         return msg
    
      File "<ipython-input-3-6bb841ea1423>", line 1
        def some_function()
                           ^
    SyntaxError: invalid syntax
    

    Here, Python tells us that there is a SyntaxError on line 1,
    and even puts a little arrow in the place where there is an issue.
    In this case the problem is that the function definition is missing a colon at the end.

    Actually, the function above has two issues with syntax.
    If we fix the problem with the colon,
    we see that there is also an IndentationError,
    which means that the lines in the function definition do not all have the same indentation:

    def some_function():
        msg = 'hello, world!'
        print(msg)
         return msg
    
      File "<ipython-input-4-ae290e7659cb>", line 4
        return msg
        ^
    IndentationError: unexpected indent
    

    Both SyntaxError and IndentationError indicate a problem with the syntax of your program,
    but an IndentationError is more specific:
    it always means that there is a problem with how your code is indented.

    Tabs and Spaces

    Some indentation errors are harder to spot than others.
    In particular, mixing spaces and tabs can be difficult to spot
    because they are both whitespace.
    In the example below, the first two lines in the body of the function
    some_function are indented with tabs, while the third line — with spaces.
    If you’re working in a Jupyter notebook, be sure to copy and paste this example
    rather than trying to type it in manually because Jupyter automatically replaces
    tabs with spaces.

    def some_function():
    	msg = 'hello, world!'
    	print(msg)
            return msg
    

    Visually it is impossible to spot the error.
    Fortunately, Python does not allow you to mix tabs and spaces.

      File "<ipython-input-5-653b36fbcd41>", line 4
        return msg
                  ^
    TabError: inconsistent use of tabs and spaces in indentation
    

    Variable Name Errors

    Another very common type of error is called a NameError,
    and occurs when you try to use a variable that does not exist.
    For example:

    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-7-9d7b17ad5387> in <module>()
    ----> 1 print(a)
    
    NameError: name 'a' is not defined
    

    Variable name errors come with some of the most informative error messages,
    which are usually of the form “name ‘the_variable_name’ is not defined”.

    Why does this error message occur?
    That’s a harder question to answer,
    because it depends on what your code is supposed to do.
    However,
    there are a few very common reasons why you might have an undefined variable.
    The first is that you meant to use a
    string, but forgot to put quotes around it:

    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-8-9553ee03b645> in <module>()
    ----> 1 print(hello)
    
    NameError: name 'hello' is not defined
    

    The second reason is that you might be trying to use a variable that does not yet exist.
    In the following example,
    count should have been defined (e.g., with count = 0) before the for loop:

    for number in range(10):
        count = count + number
    print('The count is:', count)
    
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-9-dd6a12d7ca5c> in <module>()
          1 for number in range(10):
    ----> 2     count = count + number
          3 print('The count is:', count)
    
    NameError: name 'count' is not defined
    

    Finally, the third possibility is that you made a typo when you were writing your code.
    Let’s say we fixed the error above by adding the line Count = 0 before the for loop.
    Frustratingly, this actually does not fix the error.
    Remember that variables are case-sensitive,
    so the variable count is different from Count. We still get the same error,
    because we still have not defined count:

    Count = 0
    for number in range(10):
        count = count + number
    print('The count is:', count)
    
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-10-d77d40059aea> in <module>()
          1 Count = 0
          2 for number in range(10):
    ----> 3     count = count + number
          4 print('The count is:', count)
    
    NameError: name 'count' is not defined
    

    Index Errors

    Next up are errors having to do with containers (like lists and strings) and the items within them.
    If you try to access an item in a list or a string that does not exist,
    then you will get an error.
    This makes sense:
    if you asked someone what day they would like to get coffee,
    and they answered “caturday”,
    you might be a bit annoyed.
    Python gets similarly annoyed if you try to ask it for an item that doesn’t exist:

    letters = ['a', 'b', 'c']
    print('Letter #1 is', letters[0])
    print('Letter #2 is', letters[1])
    print('Letter #3 is', letters[2])
    print('Letter #4 is', letters[3])
    
    Letter #1 is a
    Letter #2 is b
    Letter #3 is c
    
    ---------------------------------------------------------------------------
    IndexError                                Traceback (most recent call last)
    <ipython-input-11-d817f55b7d6c> in <module>()
          3 print('Letter #2 is', letters[1])
          4 print('Letter #3 is', letters[2])
    ----> 5 print('Letter #4 is', letters[3])
    
    IndexError: list index out of range
    

    Here,
    Python is telling us that there is an IndexError in our code,
    meaning we tried to access a list index that did not exist.

    File Errors

    The last type of error we’ll cover today
    are those associated with reading and writing files: FileNotFoundError.
    If you try to read a file that does not exist,
    you will receive a FileNotFoundError telling you so.
    If you attempt to write to a file that was opened read-only, Python 3
    returns an UnsupportedOperationError.
    More generally, problems with input and output manifest as
    IOErrors or OSErrors, depending on the version of Python you use.

    file_handle = open('myfile.txt', 'r')
    
    ---------------------------------------------------------------------------
    FileNotFoundError                         Traceback (most recent call last)
    <ipython-input-14-f6e1ac4aee96> in <module>()
    ----> 1 file_handle = open('myfile.txt', 'r')
    
    FileNotFoundError: [Errno 2] No such file or directory: 'myfile.txt'
    

    One reason for receiving this error is that you specified an incorrect path to the file.
    For example,
    if I am currently in a folder called myproject,
    and I have a file in myproject/writing/myfile.txt,
    but I try to open myfile.txt,
    this will fail.
    The correct path would be writing/myfile.txt.
    It is also possible that the file name or its path contains a typo.

    A related issue can occur if you use the “read” flag instead of the “write” flag.
    Python will not give you an error if you try to open a file for writing
    when the file does not exist.
    However,
    if you meant to open a file for reading,
    but accidentally opened it for writing,
    and then try to read from it,
    you will get an UnsupportedOperation error
    telling you that the file was not opened for reading:

    file_handle = open('myfile.txt', 'w')
    file_handle.read()
    
    ---------------------------------------------------------------------------
    UnsupportedOperation                      Traceback (most recent call last)
    <ipython-input-15-b846479bc61f> in <module>()
          1 file_handle = open('myfile.txt', 'w')
    ----> 2 file_handle.read()
    
    UnsupportedOperation: not readable
    

    These are the most common errors with files,
    though many others exist.
    If you get an error that you’ve never seen before,
    searching the Internet for that error type
    often reveals common reasons why you might get that error.

    Reading Error Messages

    Read the Python code and the resulting traceback below, and answer the following questions:

    1. How many levels does the traceback have?
    2. What is the function name where the error occurred?
    3. On which line number in this function did the error occur?
    4. What is the type of error?
    5. What is the error message?
    # This code has an intentional error. Do not type it directly;
    # use it for reference to understand the error message below.
    def print_message(day):
        messages = {
            'monday': 'Hello, world!',
            'tuesday': 'Today is Tuesday!',
            'wednesday': 'It is the middle of the week.',
            'thursday': 'Today is Donnerstag in German!',
            'friday': 'Last day of the week!',
            'saturday': 'Hooray for the weekend!',
            'sunday': 'Aw, the weekend is almost over.'
        }
        print(messages[day])
    
    def print_friday_message():
        print_message('Friday')
    
    print_friday_message()
    
    ---------------------------------------------------------------------------
    KeyError                                  Traceback (most recent call last)
    <ipython-input-1-4be1945adbe2> in <module>()
         14     print_message('Friday')
         15
    ---> 16 print_friday_message()
    
    <ipython-input-1-4be1945adbe2> in print_friday_message()
         12
         13 def print_friday_message():
    ---> 14     print_message('Friday')
         15
         16 print_friday_message()
    
    <ipython-input-1-4be1945adbe2> in print_message(day)
          9         'sunday': 'Aw, the weekend is almost over.'
         10     }
    ---> 11     print(messages[day])
         12
         13 def print_friday_message():
    
    KeyError: 'Friday'
    

    Solution

    1. 3 levels
    2. print_message
    3. 11
    4. KeyError
    5. There isn’t really a message; you’re supposed
      to infer that Friday is not a key in messages.

    Identifying Syntax Errors

    1. Read the code below, and (without running it) try to identify what the errors are.
    2. Run the code, and read the error message. Is it a SyntaxError or an IndentationError?
    3. Fix the error.
    4. Repeat steps 2 and 3, until you have fixed all the errors.
    def another_function
      print('Syntax errors are annoying.')
       print('But at least Python tells us about them!')
      print('So they are usually not too hard to fix.')
    

    Solution

    SyntaxError for missing (): at end of first line,
    IndentationError for mismatch between second and third lines.
    A fixed version is:

    def another_function():
        print('Syntax errors are annoying.')
        print('But at least Python tells us about them!')
        print('So they are usually not too hard to fix.')
    

    Identifying Variable Name Errors

    1. Read the code below, and (without running it) try to identify what the errors are.
    2. Run the code, and read the error message.
      What type of NameError do you think this is?
      In other words, is it a string with no quotes,
      a misspelled variable,
      or a variable that should have been defined but was not?
    3. Fix the error.
    4. Repeat steps 2 and 3, until you have fixed all the errors.
    for number in range(10):
        # use a if the number is a multiple of 3, otherwise use b
        if (Number % 3) == 0:
            message = message + a
        else:
            message = message + 'b'
    print(message)
    

    Solution

    3 NameErrors for number being misspelled, for message not defined,
    and for a not being in quotes.

    Fixed version:

    message = ''
    for number in range(10):
        # use a if the number is a multiple of 3, otherwise use b
        if (number % 3) == 0:
            message = message + 'a'
        else:
            message = message + 'b'
    print(message)
    

    Identifying Index Errors

    1. Read the code below, and (without running it) try to identify what the errors are.
    2. Run the code, and read the error message. What type of error is it?
    3. Fix the error.
    seasons = ['Spring', 'Summer', 'Fall', 'Winter']
    print('My favorite season is ', seasons[4])
    

    Solution

    IndexError; the last entry is seasons[3], so seasons[4] doesn’t make sense.
    A fixed version is:

    seasons = ['Spring', 'Summer', 'Fall', 'Winter']
    print('My favorite season is ', seasons[-1])
    

    Key Points

    • Tracebacks can look intimidating, but they give us a lot of useful information about what went wrong in our program, including where the error occurred and what type of error it was.

    • An error having to do with the ‘grammar’ or syntax of the program is called a SyntaxError. If the issue has to do with how the code is indented, then it will be called an IndentationError.

    • A NameError will occur when trying to use a variable that does not exist. Possible causes are that a variable definition is missing, a variable reference differs from its definition in spelling or capitalization, or the code contains a string that is missing quotes around it.

    • Containers like lists and strings will generate errors if you try to access items in them that do not exist. This type of error is called an IndexError.

    • Trying to read a file that does not exist will give you an FileNotFoundError. Trying to read a file that is open for writing, or writing to a file that is open for reading, will give you an IOError.

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

    • Как устроен механизм исключений
    • Как обрабатывать исключения в 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 можно ознакомиться в официальной документации.

    Понравилась статья? Поделить с друзьями:

    Читайте также:

  • Python file read memory error
  • Python fatal python error initfsencoding unable to load the file system codec
  • Python fatal error python h no such file or directory
  • Python fatal error on ssl transport
  • Python fatal error c1083

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

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