Что такое ошибка исполнения в питоне

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

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

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

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

A runtime error is a type of error that occurs during program execution. The Python interpreter executes a script if it is syntactically correct. However, if it encounters an issue at runtime, which is not detected when the script is parsed, script execution may halt unexpectedly.

What Causes Runtime Errors

Some of the most common examples of runtime errors in Python are:

  • Division by zero.
  • Using an undefined variable or function name.
  • Performing an operation on incompatible types.
  • Accessing a list element, dictionary key or object attribute that does not exist.
  • Accessing a file that does not exist.

Python Runtime Error Examples

Here’s a few examples of runtime errors in Python:

Division by zero

If a number is divided by zero in Python, a runtime error is raised:

print(100/0)

In the above example, a number is attempted to be divided by zero. Running the above code raises a ZeroDivisionError:

Traceback (most recent call last):
  File "main.py", line 1, in <module>
    print(100/0)
ZeroDivisionError: division by zero

Using an undefined variable or function name

A runtime error is raised if an attempt is made to access an identifier, such as a variable or function name, that is not declared previously:

print(myString)

In the above example, an undefined identifier myString is attempted to be accessed. Running the above code raises a NameError:

Traceback (most recent call last):
  File "main.py", line 1, in <module>
    print(myString)
NameError: name 'myString' is not defined

Performing an operation on incompatible types

If an operation, such as addition, multiplication etc., is performed between incompatible data types, a runtime error is raised:

myString = "Hello World"
myNumber = 100
print(myString + myNumber)

In the above example, a string is attempted to be concatenated with a number. Since these types are incompatible, a TypeError is raised when the above code is executed:

File "main.py", line 3, in <module>
    print(myString + myNumber)
TypeError: can only concatenate str (not "int") to str

Accessing a non-existent list element, dictionary key or object attribute

If an attempt is made to access a non-existent index or element in a list, dictionary or   object, a runtime error is raised.

numbers = [1, 2, 3]
print(numbers[3])

In the above example, an attempt is made to access an item in a list using an out-of-range index, which raises an IndexError:

Traceback (most recent call last):
  File "main.py", line 2, in <module>
    print(numbers[3])
IndexError: list index out of range.

Accessing a file that does not exist

If a file that does not exist is attempted to be accessed, a runtime error is raised:

open("myFile.txt", "r")

In the above example, a non-existent file myFile.txt is attempted to be opened in read-only mode, which raises a FileNotFoundError:

Traceback (most recent call last):
  File "main.py", line 1, in <module>
    open("myFile.txt", "r")
FileNotFoundError: [Errno 2] No such file or directory: 'myFile.txt'

How to Fix Runtime Errors in Python

To fix runtime errors in Python, the following steps can be taken:

  1. Identify the error message and note the specific problem being reported.
  2. Check the code for logical, mathematical or typographical errors.
  3. Ensure all identifiers are defined properly before being used.
  4. Make sure the correct data types are being used and are being used correctly.
  5. Verify that list items, dictionary keys, and other objects are being accessed using valid indices or keys.
  6. If necessary, consult the documentation for the relevant library or module.

Track, Analyze and Manage Errors With Rollbar

Managing errors and exceptions in your code is challenging. It can make deploying production code an unnerving experience. Being able to track, analyze, and manage errors in real-time can help you to proceed with more confidence. Rollbar automates error monitoring and triaging, making fixing Python errors easier than ever. Try it today!

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

Различные недостатки всегда будут возникать при программировании. За некоторые проблемы отвечает наш код, например, за синтаксические ошибки. В большинстве случаев такую неточность называют багом. Недостатки должны исправляться.

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

  • Диск может быть заполнен во время записи данных в файлы и препятствовать дальнейшей записи.
  • Неожиданно обрушилась сеть во время загрузки той или иной информации.
  • Использование 0 в знаменателе при делении.

Разница между ошибкой и исключением

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

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

Типы ошибок

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

Расположение букв и символов в коде называется синтаксисом. Поэтому, если вы встречаете синтаксическую ошибку, это обычно означает, что в вашем коде пропущен символ или буква. Python будет часто информировать вас о строке и местоположении проблемы. Сообщение об ошибке следует читать очень внимательно.

Блок кода:

num1 = 2
num2 = 3
Num3 = num1 num2
print(num3)

Вывод:

Ошибка нехватки памяти

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

Ошибка рекурсии

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

Блок кода:

def abc():
return abc()
abc()

Вывод:

Ошибка отступа

Python является процедурным языком, поэтому если вы не поместите табулятор или пробелы между строкам вашего кода, то, вероятно, получите ошибку Indentation Error.

Блок кода:

for i in range(3):
print(i)

Вывод:

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

Вы можете с удобством обслуживать исключения, благодаря встроенной в Python системе обработки исключений. Это механизм try…except…finally.

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

Обычные типы исключений в Python

Ошибка атрибута

Ошибки, связанные со ссылками на атрибуты или присвоением.

Блок кода:

arr = [1,2,3,4,5]
arr.len()

Вывод:

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

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

Блок кода:

arr = [1,2,3,4,5]
print(arr[200])

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

Когда целое делится на 0, возникает Zero Division Error.

Блок кода:

arr = 1
arr = 0
print(a/b)

Вывод:

Ошибка типа

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

Блок кода:

arr = [1,2,3,4,5]
arr.insert(1)

Вывод:

Ошибка значения

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

Блок кода:

import math
math.sqrt(-33)

Вывод:

Ошибка имени

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

Блок кода:

print(Num)

Вывод:

Ошибка ключа

Эта ошибка возникает, когда словарь не включает заданный ключ.

Блок кода:

dic={"name":"achyut","age":20}
print(dic["value"])

Вывод:

Ошибка импорта

Эта ошибка возникает, когда модуль не может быть загружен оператором import.

Блок кода:

from math import add

Вывод:

Предложение Try и оператор Except

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

Синтаксис

try:
#возможный блок кода, который может вызвать исключение
except Exception:
#Код, который реагирует на возникающую ошибку

Замечание. Один блок try может обрабатывать множество блоков except для различных исключений.

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

Пример

a = input ("enter the Numerator:")
b = input ("enter the Denominator:")
try:
res = int(a) / int(b)
print ('result is:', res)
except ZeroDivisionError as e:
print ('exception is:', e)

Вывод:

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

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

Try с предложением Finally

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

Пример блока Finally

Блок кода:

a = input ("enter the Numerator:")
b = input ("enter the Denominator:")
try:
res = int(a) / int(b)
print ('result is:', res)
except ZeroDivisionError as e:
print ('exception is:', e)
finally:
print ("thank you")

Вывод:

Try с предложением Else

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

Пример блока Else

Блок кода:

a = input ("enter the Numerator:")
b = input ("enter the Denominator:")
try:
res = int(a) / int(b)
print ('result is:', res)
except ZeroDivisionError as e:
print ('exception is :', e)
else:
print("hello")
finally:
print ("thank you")

Вывод:

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

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

Пример

Блок кода:

def name_exception():
try:
value = "achyut tripathi"
return xyz
except NameError:
return "NameError occurred"

print(name_exception())

Вывод:

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

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

Пример

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

Блок кода:

def range_function(range):
try:
if range > 50 or range <0:
raise Exception()
if range < 100 and range > 0:
print("Range is ok")
except:
print("Exception Occurred")
range_function(150)

Вывод:

Заключение

Примеры в этом руководстве показывают как использовать в Python блок «try-except-finally».

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

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))
    

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

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

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

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

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

Содержание

  • Известные ошибки в ПО

    • 1962 г.: ракета Маринер-1

    • 1985 г.: аппарат лучевой терапии Therac-25

    • 1991 г.: ЗРК Patriot

    • 2000 г.: Проблема 2000 года (Y2K)

    • 2009-2011 г.: отзыв автомобилей Toyota

  • Определение и разновидности ошибок

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

    • Логические (семантические) ошибки

    • Ошибки времени выполнения

    • Недокументированное поведение

  • Поиск ошибок и отладка программы

  • Подходы к обработке ошибок

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

    • Понятия исключения

    • Конструкция try

    • Возбуждение исключений (raise)

    • Особенности обработки исключений внутри функций

    • Утверждения (assert)

    • Исключения или утверждения?

  • Рекомендации

7.1.1. Известные ошибки в ПО¶

История знает множество примеров, где программные ошибки стоили не только огромных денег, но и человеческих жизней 6 7:

7.1.1.1. 1962 г.: ракета Маринер-1¶

Маринер-1 — космический аппарат США для изучения Венеры (Рисунок 7.1.1).

_images/07_01_01.png

Рисунок 7.1.1 — Ракета Маринер-1 9

  • Описание и причина:

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

  • Примерный ущерб / потери

    Никто не погиб, однако экономические потери составили 18,3 млн. долларов.

7.1.1.2. 1985 г.: аппарат лучевой терапии Therac-25¶

Therac-25 — канадский аппарат лучевой терапии (Рисунок 7.1.2).

_images/07_01_02.png

Рисунок 7.1.2 — Аппарат лучевой терапии `Therac-25 10

  • Описание и причина:

    Неисправность была вызвана тем, что в проекте использовались библиотеки с ошибками, входящие в состав ПО аппарата Therac-20, что и привело к фатальным последствиям. В коде была найдена довольно распространенная ошибка многопоточности, называемое состоянием гонки. Тем не менее ошибку не заметили, так как Therac-20 работал исправно из-за дополнительных (аппаратных) мер предосторожности.

  • Примерный ущерб / потери

    Умерло 2 человека, 4 получили серьезное облучение.

7.1.1.3. 1991 г.: ЗРК Patriot¶

Patriot — американский зенитный ракетный комплекс (Рисунок 7.1.3)

_images/07_01_03.png

Рисунок 7.1.3 — ЗРК Patriot 11

  • Описание и причина:

    Во время Войны в Персидском заливе по казармам подразделений США был нанесен ракетный удар иракскими ракетами типа Р-17 (советская баллистическая ракета). Ни одна из ракет не была перехвачена, и удар достиг цели.

    В программном обеспечении ЗРК, отвечающем за ведение и перехват цели, присутствовала ошибка, из-за которой со временем внутренние часы постепенно отходили от истинного значения времени: системное время хранилось как целое число в 24-битном регистре с точностью до 0,1 секунды; при итоговом расчете данные переводились в вещественное число.

    Проблема заключалась в том, что число (cfrac{1}{10}) не имеет точного представления в двоичной системе счисления:

    • (cfrac{1}{10}_{10} = 0,0001100110011001100110011001100…_{2});

    • (cfrac{1}{10}_{10} = 0,00011001100110011001100_{2}) (24-битное целое в системе Patriot);

    • ошибка 1 измерения: ~ (0,000000095_{10});

    • ошибка за 100 часов работы (0,000000095 cdot 10 cdot 60 cdot 60 cdot 100 = 0,34) с.;

    • ракета Р-17 летит со скоростью 1676 м/c, и проходит за 0,34 с. больше полукилометра.

    Данной ошибки в измерениях было достаточно, чтобы ракета преодолела радиус поражения Patriot (Рисунок 7.1.4, Видео 7.1.1).

    _images/07_01_04.png

    Рисунок 7.1.4 — Неправильное определение зоны пролета ракеты 12

    Видео 7.1.1 — Демонстрация ошибки ПО Patriot

  • Примерный ущерб / потери

    Погибло 28 американских солдат и еще двести получили ранения.

7.1.1.4. 2000 г.: Проблема 2000 года (Y2K)¶

  • Описание и причина:

    Разработчики программного обеспечения, выпущенного в XX веке, зачастую использовали два знака для представления года в датах: например, 1 января 1961 года представлялось как «01.01.61». При наступлении 1 января 2000 года при двузначном представлении года после 99 наступал 00 год (т.е. 99 + 1 = 00), что интерпретировалось многими старыми программами как 1900 год. Сложность была еще и в том, что многие программы обращались к вычислению дат вперед (например, при составлении плана закупок, планировании даты полета и т.д.) (Рисунок 7.1.5).

    _images/07_01_05.png

    Рисунок 7.1.5 — Табло показывает 3 января 1900 года, вместо 3 января 2000 года. Франция 13

  • Примерный ущерб / потери

    30-300 млрд. долларов.

7.1.1.5. 2009-2011 г.: отзыв автомобилей Toyota¶

  • Описание и причина:

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

    Видео 7.1.2 — Случайное ускорение автомобиля

    В ходе десятимесячного расследования специалисты NASA выявили, что программное обеспечение не соответствует стандартам MISRA (англ. Motor Industry Software Reliability Association) и содержит 7134 нарушения. Представители Toyota ответили, что у них свои собственные стандарты.

    20 декабря 2010 года Тойота отвергнула обвинения, но выплатила 16 млрд. долларов в досудебном порядке по искам, выпустила обновление ПО для некоторых моделей машин и отозвала 5,5 млн. автомобилей 8 (Рисунок 7.1.6).

    _images/07_01_06.png

    Рисунок 7.1.6 — Lexus ES 350 2007-2010 — одна из моделей с неисправностью 14

  • Примерный ущерб / потери

    Погибло не менее 89 человек, многомиллиардные потери компании.

7.1.2. Определение и разновидности ошибок¶

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

Основные категории ошибок:

  • синтаксические;

  • логические;

  • ошибки времени выполнения;

  • недокументированное поведение.

7.1.2.1. Синтаксические ошибки¶

  • Причина:

    Несоответствие синтаксису языка программирования. Для компилируемых языков программирования синтаксическая ошибка не позволит выполнить компиляцию.

  • Пример:

    >>> for i in range(10)
      File "<stdin>", line 1
        for i in range(10)
                         ^
    SyntaxError: invalid syntax
    

7.1.2.2. Логические (семантические) ошибки¶

  • Причина:

    Несоответствие правильной логике работы программы.

  • Пример:

    >>> def avg_of_2(a, b):
    ...     return a + b / 2
    ...
    >>> avg_of_2(4, 8)  # Вернет 8 вместо 6
    

7.1.2.3. Ошибки времени выполнения¶

  • Причина:

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

  • Пример:

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

7.1.2.4. Недокументированное поведение¶

  • Причина:

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

  • Пример:

    Одним из наиболее известных примеров являются SQL-инъекции — внедрение в запрос произвольного SQL-кода.

    -- Код на сервере
    txtUserId = getRequestString("UserId");
    txtSQL = "SELECT * FROM Users WHERE UserId = " + txtUserId;
    
    -- "Стандартный" вызов на клиенте при UserId = 105 сформирует запрос
    SELECT * FROM Users WHERE UserId = 105
    
    -- Передача в качестве 'UserId' значения
    -- "105; DROP TABLE Suppliers" приведет к удалению таблицы 'Suppliers'
    SELECT * FROM Users WHERE UserId = 105; DROP TABLE Suppliers
    

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

Видео 7.1.3 — Презентация ОС Windows 98

7.1.3. Поиск ошибок и отладка программы¶

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

Существуют две взаимодополняющие технологии отладки:

  • использование отладчиков: программ, которые включают в себя пользовательский интерфейс для пошагового выполнения программы: оператор за оператором, функция за функцией, с остановками на некоторых строках исходного кода или при достижении определенного условия (Рисунок 7.1.7);

    _images/07_01_07.png

    Рисунок 7.1.7 — Пример отладки в IDE PyCharm: выполнение «заморожено» на точке останова (англ. Breakpoint), при этом IDE отображает текущие значения переменных, дополнительные окна и параметры

  • вывод текущего состояния программы с помощью расположенных в критических точках программы операторов вывода — на экран, принтер, громкоговоритель или в файл. Вывод отладочных сведений в файл называется журналированием (также логгированием или аудитом) (Листинг 7.1.1).

    Листинг 7.1.1 — Пример отладочного вывода с использованием функции print() | скачать

    import random
    
    a = random.randint(1, 100)
    b = random.randint(1, 100)
    
    print(a, b)  # 44 97
    
    print(a**2 + b**2)  # 11345
    

7.1.4. Подходы к обработке ошибок¶

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

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

  1. «Семь раз отмерь, один раз отрежь» — LBYL (англ. Look Before You Leap);

    Суть подхода: прежде чем выполнить основное действие выполняются проверки — не получится ли деления на ноль, есть ли файл на диске и т.д.

  2. «Легче попросить прощения, чем разрешения» — EAFP (англ. «It’s Easier To Ask Forgiveness Than Permission»).

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

В Листинге 7.1.2 приведен пример сравнения двух подходов.

Листинг 7.1.2 — Псевдокод функций, использующих разные подходы к обработке ошибок: «Семь раз отмерь, один раз отрежь» и «Легче попросить прощения, чем разрешения»

Обе функции возвращают решение линейного уравнения и НИЧЕГО, если 'a' = 0


ФУНКЦИЯ найти_корень_1(a, b):
    ЕСЛИ a не равно 0
        ВЕРНУТЬ -b / a
    ИНАЧЕ
        ВЕРНУТЬ НИЧЕГО


ФУНКЦИЯ найти_корень_2(a, b):
    ОПАСНЫЙ БЛОК КОДА      # Внутри данного блока пишется код, который
        ВЕРНУТЬ -b / a     # потенциально может привести к ошибкам
    ЕСЛИ ПРОИЗОШЛА ОШИБКА  # В случае деления на 0 попадаем сюда
        ВЕРНУТЬ НИЧЕГО

Подход «Семь раз отмерь, один раз отрежь» имеет определенные минусы:

  • проверки могут уменьшить читаемость и ясность основного кода;

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

  • разработчик может легко допустить ошибку, забыв какую-либо из проверок;

  • ситуация может изменится между моментом проверки и моментом выполнения операции.

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

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

7.1.5.1. Понятия исключения¶

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

  • BaseException (базовое исключение)

    • SystemExit (исключение, порождаемое функцией sys.exit() при выходе из программы)

    • KeyboardInterrupt (прерывании программы пользователем, Ctrl+C)

    • Exception (базовое несистемное исключение)

      • ArithmeticError (арифметическая ошибка)

        • FloatingPointError (неудачное выполнение операции с плавающей запятой)

        • OverflowError (результат арифметической операции слишком велик для представления)

        • ZeroDivisionError (деление на ноль)

      • LookupError (некорректный индекс или ключ)

        • IndexError (индекс не входит в диапазон элементов)

        • KeyError (несуществующий ключ)

      • MemoryError (недостаточно памяти)

      • NameError (не найдено переменной с таким именем)

      • OSError (ошибка, связанная с ОС — есть подклассы, например FileNotFoundError)

      • SyntaxError (синтаксическая ошибка, включает классы IndentationError и TabError)

      • SystemError (внутренняя ошибка)

      • TypeError (операция применена к объекту несоответствующего типа)

      • ValueError (аргумент правильного типа, но некорректного значения)

Пример встроенного возбуждения исключения:

>>> "я - строка" / 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'int'

7.1.5.2. Конструкция try

Придерживаясь идеологии «Легче попросить прощения, чем разрешения», Python предусматривает конструкцию try для обработки возникающих исключений.

try
except
else
finally
try:                              # (try строго 1)
    try_ suite                    # код, который может выполниться с ошибкой
except exception_group1 as var1:  # (except - 0 (если есть finally) и более)
    except_suite1                 # код, выполняемый в случае исключения 'exception_group1'
...                               # ссылка на исключение может быть записана в 'var1'
except exception_groupN as varN:
    except_suiteN                 # код, выполняемый в случае исключения 'exception_groupN'
...                               # except-блоков может быть произвольное кол-во
else:                             # (else - 0 или 1)
    else_suite                    # выполняется, если try не завершен преждевременно (например, break)
finally:                          # (finally - 0 или 1)
    finally_suite                 # код, который должен выполнится всегда (была ошибка выше или нет)

Ход выполнения:

  • код, который потенциально может привести к ошибке, помещается в блок try;

  • в случае ошибки, код немедленно завершается и переходит в обработчик except (если он указан для соответствующего исключения);

  • после поток выполнения переходит к else (если исключений не было) и finally (в любом случае).

На Рисунке 7.1.8 приведены общие варианты потока выполнения программы при обработке исключений.

_images/07_01_08.png

Рисунок 7.1.8 — Варианты потока выполнения программы при обработке исключений 4

Обработка исключений (и соответственно идеология «Легче попросить прощения, чем разрешения») — предпочитаемый способ в Python, а использование блоков зависит от конкретной ситуации.

Наиболее общий вариант обработки исключений приведен в Листинге 7.1.3.

Листинг 7.1.3 — Наиболее простой способ обработки исключений | скачать

try:
    x = int(input("Введите целое число x (для вычисления 1/x): "))
    res = 1 / x

    print("1/{} = {:.2f}".format(x, res))
except:
    print("Произошла ошибка!")

# --------------
# Примеры вывода:

# Введите целое число x (для вычисления 1/x): 3
# 1/3 = 0.33

# Введите целое число x (для вычисления 1/x): qwerty
# Произошла ошибка!

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

Листинг 7.1.4 — Обработка общего класса исключений Exception | скачать

try:
    x = int(input("Введите целое число x (для вычисления 1/x): "))
    res = 1 / x

    print("1/{} = {:.2f}".format(x, res))
except Exception as err:
    print("Произошла ошибка!")
    print("Тип:", type(err))
    print("Описание:", err)

# --------------
# Примеры вывода:

# Введите целое число x (для вычисления 1/x): 3
# 1/3 = 0.33

# Введите целое число x (для вычисления 1/x): 5.5
# Произошла ошибка!
# Тип: <class 'ValueError'>
# Описание: invalid literal for int() with base 10: '5.5'

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

Листинг 7.1.5 — Обработка конкретных классов исключений | скачать

try:
    x = int(input("Введите целое число x (для вычисления 1/x): "))
    res = 1 / x

    print("1/{} = {:.2f}".format(x, res))
except ZeroDivisionError:
    print("На ноль делить нельзя!")
except ValueError as err:  # 'err' содержит ссылку на исключение
    print("Будьте внимательны:", err)
except (FileExistsError, FileNotFoundError):  # Исключения можно перечислять в виде кортежа
    print("Этого никогда не случится - мы не работаем с файлами")
except Exception as err:
    # Все, что не обработано выше и является потомком 'Exception',
    # будет обработано здесь
    print("Произошла ошибка!")
    print("Тип:", type(err))
    print("Описание:", err)

# --------------
# Примеры вывода:

# Введите целое число x (для вычисления 1/x): 3
# 1/3 = 0.33

# Введите целое число x (для вычисления 1/x): 0
# На ноль делить нельзя!

# Введите целое число x (для вычисления 1/x): qwerty
# Будьте внимательны: invalid literal for int() with base 10: 'qwerty'

7.1.5.3. Возбуждение исключений (raise

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

raise
raise exception(args)  # явное указание класса возбуждаемого исключения

# или

raise                  # 1) повторное возбуждение активного исключения (re-raise)
                       #    внутри блока except
                       # 2) 'TypeError' по умолчанию

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

В Листинге 7.1.6 приведен пример использование оператора raise.

Листинг 7.1.6 — Использование raise для управления потоком выполнения | скачать

MIN = 1
MAX = 10

try:
    x = int(input("Введите целое число от {} до {}: ".format(MIN, MAX)))

    if not MIN <= x <= MAX:
        # Возбудив исключение, его можно будет обработать в except
        # вместе с другими похожими исключениями
        raise ValueError("Число лежит вне интервала [{}; {}]!".format(MIN, MAX))

    print("Спасибо!")
except ValueError as err:  # 'err' содержит ссылку на исключение
    print("Будьте внимательны:", err)

# --------------
# Примеры вывода:

# Введите целое число от 1 до 10: 5
# Спасибо!

# Введите целое число от 1 до 10: 15
# Будьте внимательны: Число лежит вне интервала [1; 10]!

# Введите целое число от 1 до 10: qwerty
# Будьте внимательны: invalid literal for int() with base 10: 'qwerty'

7.1.5.4. Особенности обработки исключений внутри функций¶

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

Разница в обработке исключений приведена в Листинге 7.1.7.

Листинг 7.1.7 — Различная обработка исключений в функции | скачать

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


def get_1_x(x):
    """Вернуть 1/x.

    Функция не обрабатывает исключения - ответственность на вызывающем коде.
    """
    return 1/x


def get_2_x(x):
    """Вернуть 2/x.

    Функция обрабатывает исключения, "затушив" ошибку - вызывающий код
    не будет знать, сработала функция правильно или нет.

    Данный способ использовать не рекомендуется!
    """
    try:
        return 2/x
    except Exception as e:
        print("Внутри произошла ошибка...", e)


def get_3_x(x):
    """Вернуть 3/x.

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

    Внутренняя обработка исключений может быть полезна, если в целом результат
    функции не связан с внутренней ошибкой.
    """
    try:
        return 3/x
    except Exception as e:
        print("Внутри произошла ошибка...", e)
        raise

funcs = (get_1_x, get_2_x, get_3_x)
# Вызываем каждую функцию с "ошибочным" параметром
for func in funcs:
    try:
        print("-" * 50)
        print("Запущена функция:", func.__name__)
        print(func(0))
    except Exception as e:
        print("Произошла ошибка: {}.".format(e))

# -------------
# Пример вывода:

# --------------------------------------------------
# Запущена функция: get_1_x
# Произошла ошибка: division by zero.
# --------------------------------------------------
# Запущена функция: get_2_x
# Внутри произошла ошибка... division by zero
# None
# --------------------------------------------------
# Запущена функция: get_3_x
# Внутри произошла ошибка... division by zero
# Произошла ошибка: division by zero.

7.1.5.5. Утверждения (assert

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

В Python утверждения поддерживаются оператором assert.

assert
assert boolean_expression[, optional_expression]

# boolean_expression: логическое выражение для проверки
# optional_expression: необязательное сообщение (строка)

Если boolean_expression возвращает False, возбуждается исключение AssertionError с сообщением optional_expression (если задано).

Пример использования утверждений приведен в Листинге 7.1.8.

Листинг 7.1.8 — Использование утверждений в Python | скачать

# Использование оператора assert
# поможет отследить неверно реализованную функцию


def add_to_list(x, lst=[]):
    # Использование assert здесь оправдано - список всегда
    # подразумевается пустым
    assert len(lst) == 0, "Список должен быть пуст!"

    lst.append(x)
    return lst


print(add_to_list(1))
print(add_to_list(2))

# -------------
# Пример вывода:

# [1]
# Traceback (most recent call last):
#   File "07_01_08_a.py", line 15, in <module>
#     print(add_to_list(2))
#   File "07_01_08_a.py", line 8, in add_to_list
#     assert len(lst) == 0, "Список должен быть пуст!"
# AssertionError: Список должен быть пуст!

Примечание

В отличие от исключений утверждения являются отладочным инструментом и могут быть отключены при компиляции/интерпретации программы

7.1.5.6. Исключения или утверждения?¶

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

Таблица 7.1.1 — Использовать исключения или утверждения?

Исключения

Утверждения

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

Проверка ситуаций, которые предположительно не могут произойти

1

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

Проверка предусловий, постусловий и инвариантов в необщедоступном коде (локальных функциях, внутреннем коде и т.д.)

2

Предоставление информации об ошибке пользователю

Предоставление информации об ошибке команде разработки

3

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

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

Примеры использования исключения и утверждений приведены в Листингах 7.1.9 (а-в).

Листинг 7.1.9 (а) — Использование исключений и утверждений в Python | скачать

# Исключения и утверждения могут проверять параметры функции,
# выступая в т.ч. более строгим вариантом документации


def fact(x):
    """Вернуть факториал 'x'.

    Не передавайте числа больше 15 во избежание переполнения памяти.
    """
    if x <= 1:
        return 1
    else:
        return x * fact(x-1)


def fact_save_1(x):
    assert x <= 15, 
        "Не передавайте числа больше 15 во избежание переполнения памяти"

    return fact(x)


def fact_save_2(x):
    if not x <= 15:
        raise ValueError("Не передавайте числа больше 15 во избежание "
                         "переполнения памяти")

    return fact(x)


print("{:>3} {:>20} {:>20}".format(*("x", "fact()", "fact_save()")))
for x in (5, 20):
    print("{:3}".format(x), end=" ")
    print("{:20}".format(fact(x)), end=" ")
    # print("{:20}".format(fact_save_1(x)))
    print("{:20}".format(fact_save_2(x)))

# -------------
# Пример вывода:

#   x               fact()          fact_save()
#   5                  120                  120
#  20  2432902008176640000 Traceback (most recent call last):
#   File "07_01_08_b.py", line 23, in <module>
#     print("{:20}".format(fact_save(x)))
#   File "07_01_08_b.py", line 15, in fact_save
#     assert x <= 15, "Не передавайте числа больше 15 во избежание переполнения памяти"
# AssertionError: Не передавайте числа больше 15 во избежание переполнения памяти
#
#   x               fact()          fact_save()
#   5                  120                  120
#  20  2432902008176640000 Traceback (most recent call last):
#   File "07_01_08_b.py", line 34, in <module>
#     print("{:20}".format(fact_save_2(x)))
#   File "07_01_08_b.py", line 24, in fact_save_2
#     raise ValueError("Не передавайте числа больше 15 во избежание "
# ValueError: Не передавайте числа больше 15 во избежание переполнения памяти

Листинг 7.1.9 (б) — Использование исключений и утверждений в Python | скачать

# Использование оператора assert для проверки входных и выходных данных


def make_call(accounts, account_id, mins, costs_per_min):
    """Списать со счета 'account' на 'value' баллов в случае звонка.

    Параметры:
        - accounts (dict): словарь со всеми счетами абонентов;
        - account_id (int): идентификатор абонента в словаре 'accounts';
        - mins (int): количество минут разговора;
        - costs_per_min (int): стоимость минуты разговора.
    """

    def get_costs(mins, costs_per_min):
        """Вернуть стоимость звонка.

        Параметры:
            - mins (int): количество минут разговора;
            - costs_per_min (int): стоимость минуты разговора.
        """
        return -mins * costs_per_min

    # Проверка типов
    assert isinstance(mins, int), "Параметр 'mins' имеет неверный тип!"
    assert isinstance(costs_per_min, (int, float)),
        "Параметр 'costs_per_min' имеет неверный тип!"

    # Проверка значений
    assert mins > 0, "Параметр 'mins' должен быть > 0"
    assert costs_per_min >= 0, "Параметр 'costs' должен быть >= 0"

    # Расчет (стоимость звонка не должна быть меньше 0)
    costs_total = get_costs(mins, costs_per_min)
    assert costs_total >= 0,
        "Расчет стоимости звонка был осуществлен неверно!"
    accounts[account_id] -= costs_total


# Словарь ID=Баланс
accounts = {"Василий Иванов": 100}
print(accounts)

try:
    make_call(accounts, "Василий Иванов", mins=4, costs_per_min=2)
except Exception as e:
    print("Во время списывания стоимости звонка произошла ошибка:", e)

print(accounts)

Листинг 7.1.9 (в) — Использование исключений и утверждений в Python | скачать

# Совместное использование исключений и утверждений

weekday_names = {
    1: "Понедельник",
    2: "Вторник",
    3: "Среда",
    4: "Четверг",
    5: "Пятница",
    6: "Суббота",
    7: "Воскресенье"
}


def weekday_name(weekday):
    """Вернуть название дня недели. Нумерация с 1.

    Параметры:
        weekday (int): номер дня недели.

    Исключения:
        - TypeError: 'weekday' не int;
        - ValueError: 'weekday' не число от 1 до 7.

    Результат:
        str: название дня недели.
    """
    # "Невозможная" ситуация - словарь 'weekday_names' может быть
    # "испорчен" - проверяется с помощью assert.
    assert weekday_names is not None and isinstance(weekday_names, dict), 
        "Внутренняя ошибка программы. Обратитесь в разрабочику."

    # Параметры функции проверяются с помощью исключений
    if not isinstance(weekday, int):
        raise TypeError("Параметр 'weekday' должен быть типа 'int'.")
    if weekday not in weekday_names:
        raise ValueError("Параметр 'weekday' должен быть целым числом "
                         "от 1 до 7.")

    return weekday_names[weekday]


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

while True:
    try:
        weekday = int(input("Введите номер дня недели (1-7): "))

        # if not 1 <= weekday <= 7:
        #     raise ValueError("Номер дня недели должен быть целым числом "
        #                      "от 1 до 7.")

        # Раскомментируйте код ниже, чтобы получить срабатывание assert
        # weekday_names = None

        print("Это -", weekday_name(weekday))

        break
    except TypeError as err:
        print("Проверьте, что введено целое число.")
    except ValueError as err:
        print("Проверьте, что введено целое число, и оно "
              "находится в допустимых границах.")
    except Exception as err:
        print("Ошибка при определении названия дня недели.")
        print(err)  # Запись в лог информации об ошибке

7.1.6. Рекомендации¶

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

  • код, который потенциально может привести к ошибкам, должен быть помещен в блок try;

  • блок except должен:

    • обрабатывать исключения максимально конкретно (указывать конкретные классы); стоит определять свои классы исключений, когда это это имеет смысл;

    • категорически не следует «тушить» исключения (писать пустой или бессмысленный except);

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

  • блок finally следует использовать для освобождения ресурсов (это может быть закрытие файла или сетевого соединения), независимо от того, прошла операция успешно или нет.


1

Sebesta, W.S Concepts of Programming languages. 10E; ISBN 978-0133943023.

2

Python — официальный сайт. URL: https://www.python.org/.

3

Python — FAQ. URL: https://docs.python.org/3/faq/programming.html.

4

Саммерфилд М. Программирование на Python 3. Подробное руководство. — М.: Символ-Плюс, 2009. — 608 с.: ISBN: 978-5-93286-161-5.

5

Лучано Рамальо. Python. К вершинам мастерства. — М.: ДМК Пресс , 2016. — 768 с.: ISBN: 978-5-97060-384-0, 978-1-491-94600-8.

6

5 худших багов в истории. URL: https://tproger.ru/articles/5-worst-bugs-in-history/.

7

List of software bugs. URL: https://en.wikipedia.org/wiki/List_of_software_bugs.

8

Toyota: 81 514 нарушений в коде. URL: https://habrahabr.ru/company/pvs-studio/blog/310862/.

9

Mariner-1. URL: https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%80%D0%B8%D0%BD%D0%B5%D1%80-1#/media/File:Atlas_Agena_with_Mariner_1.jpg.

10

Therac-25. URL: https://upload.wikimedia.org/wikipedia/commons/b/bd/Clinac_2rtg.jpg.

11

ЗРК Patriot. URL: http://vpk.name/file/img/Patriot_antimissile.t.jpg.

12

Incorrectly Calculated Range Gate. URL: https://hsto.org/files/853/04a/1dc/85304a1dc1b6499d94d5394436e42faf.jpg.

13

Табло показывает 3 января 1900 года, вместо 3 января 2000 года. Франция. URL: https://upload.wikimedia.org/wikipedia/commons/f/fb/Bug_de_l%27an_2000.jpg.

14

MY 2007–2010 Lexus ES 350. URL: http://pictures.topspeed.com/IMG/crop/201108/lexus-es-350-6_1600x0w.jpg.

Отладка¶

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

  1. Синтаксические ошибки — ошибки, обнаруживаемые Python в ходе трансляции
    исходного кода в байт-код. Такие ошибки вызваны тем, что что-то не так с
    синтаксисом программы. Пример: забыв поставить двоеточие в конце строки def,
    получим такое сообщение об ошибке: SyntaxError: invalid syntax.
  2. Ошибки времени выполнения — ошибки, возникающие при выполнении программы
    в случае, если происходит что-то неожиданное. Большинство ошибок времени выполнения
    включают информацию о том, где произошла ошибка и какие функции при этом выполнялись.
    Пример: бесконечная рекурсия в конце концов приводит к ошибке времени выполнения
    из-за превышения максимальной разрешенной глубины рекурсии.
  3. Семантические ошибки — логические ошибки в программе, которая компилируется и выполняется,
    но делает не то, что от нее ожидается. Пример: Выражение может быть вычислено не в том
    порядке, как ожидает программист, не позаботившийся о скобках.

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

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

Синтаксическую ошибку легко исправить, как только вы поняли, в чем она состоит. К сожалению, сообщения об ошибках часто оказываются не очень полезны. Наиболее частые сообщения SyntaxError: invalid syntax и SyntaxError: invalid token не очень информативны.

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

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

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

Вот несколько советов, как избежать самых распространенных синтаксических ошибок:

  1. Убедитесь, что вы не используете одно из ключевых слов Python как идентификатор
    (имя переменной или функции).
  2. Проверьте, что не забыли поставить двоеточие в конце заголовка каждого составного
    предложения, включая for, while, if и def.
  3. Проверьте, что отступы в вашей программе сделаны единообразно. Можете
    пользоваться пробелами или табуляцией, но лучше не смешивать одно с другим.
    Все строки каждого уровня вложенности кода должны иметь одинаковый отступ.
  4. Убедитесь, что все строковые литералы в коде имеют открывающие и закрывающие кавычки.
  5. Если в программе имеются строковые литералы в тройных кавычках,
    занимающие несколько строк в файле, убедитесь, что они корректно
    завершены. Незавершенная строка может привести к ошибке invalid token в конце
    вашей программы, или к тому, что последующая часть программы будет восприниматься
    как продолжение строки (пока не встретится другая строка). В последнем случае
    может даже не быть сообщения об ошибке!
  6. Незакрытая скобка – (, { или [ – заставляет Python полагать, что следующая строка
    является продолжением текущей. В этом случае Python почти всегда сообщает об ошибке в
    следующей строке.
  7. Проверьте, что в условии используется оператор ==, а не =.

Если ничто из этого не помогло, переходите к следующему разделу…

Не могу запустить программу, что бы я ни делал¶

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

Если это случилось, можно создать новую программу (вроде Hello World!) и убедиться, что она-то запускается. Затем постепенно добавляйте необходимый код к работающей программе.

Ошибки времени выполнения¶

Если ваша программа синтаксически корректна, Python, как минимум, начнет ее выполнение. Что теперь может пойти не так?

Моя программа совсем ничего не делает¶

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

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

Моя программа зависает¶

Если программа “останавливается” и как-будто ничего не делает, то говорят, что программа зависла. Часто это означает, что программа вошла в бесконечный цикл.

  1. Если вы подозреваете какой-то определенный цикл, то добавьте предложение
    print непосредственно перед циклом, которое сообщит о входе в цикл, и
    еще одно сразу после цикла, сообщающее о выходе из него.
  2. Запустите программу. Если вы видите первое сообщение, но не видите второго,
    то программа вошла в бесконечный цикл. См. раздел Бесконечный цикл ниже.
  3. Что касается неограниченной рекурсии, то в этом случае программа будет выполняться
    какое-то время, пока не выдаст ошибку RuntimeError: Maximum recursion depth exceeded.
    Если это случилось, см. раздел Неограниченная рекурсия ниже.
  4. Если программа не выдает такую ошибку, но вы подозреваете, что проблема в
    рекурсивном методе или функции, приемы, описанные в разделе Неограниченная рекурсия,
    все же могут оказаться полезны.
  5. Если вышеописанные шаги не работают, проверьте другие циклы или другие рекурсивные
    функции и методы.
  6. Если и это не помогает, то вы, вероятно, не понимаете последовательность
    выполнения предложений в вашей программе. См. раздел Поток выполнения ниже.

Бесконечный цикл¶

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

Например:

while x > 0 and y < 0:
    # do something to x
    # do something to y

    print  "x: ", x
    print  "y: ", y
    print  "condition: ", (x > 0 and y < 0)

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

Неограниченная рекурсия¶

В большинстве случаев неограниченная рекурсия приведет к ошибке RuntimeError: Maximum recursion depth exceeded.

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

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

Поток выполнения¶

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

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

При выполнении программы возникает исключение¶

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

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

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

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

Здесь несколько возможных причин:

  1. Вы пытаетесь неправильно использовать значение. Пример: индексирование
    строки, списка или кортежа значением, отличным от числа.
  2. Имеется несоответствие между строкой формата и элементами, которые должны
    быть в нее подставлены. Либо передается неверное количество элементов, либо
    в строке формата указано недопустимое для данного элемента преобразование.
  3. Вы передали неправильное число аргументов в функцию или метод.
    Для методов, посмотрите на определение метода и проверьте, что первым параметром
    является self. Затем посмотрите на вызов метода; убедитесь, что вызываете
    метод для объекта правильного типа, и что все аргументы корректны.
KeyError
Вы пытаетесь получить доступ к элементу словаря, используя ключ, которого нет в словаре.
AttributeError
Вы пытаетесь получить доступ к атрибуту или методу, который не существует.
IndexError
Индекс, который вы используете для доступа к списку (строке или кортежу), больше,
чем длина списка минус один. Непосредственно перед строкой, в которой возникает ошибка,
вставьте предложение print, которое выведет индекс и длину списка.
Длина правильная? А индекс?

Слишком много данных выводится на печать¶

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

Чтобы упростить выводимые данные, можно удалить или закомментировать предложения print, которые уже не нужны, либо объединить отдельные print в один и отформатировать выводимые данные для более легкого восприятия.

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

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

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

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

Семантические ошибки¶

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

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

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

Моя программа не работает¶

Спросите себя:

  1. Есть ли что-то, что программа должна делать, но это не делается?
    Найдите ту часть кода, которая отвечает за эту функциональность, и
    убедитесь, что она выполняется тогда, когда вы этого ожидаете.
  2. Происходит ли что-то, чего не должно происходить? Найдите в программе
    код, отвечающий за это, и выясните, почему он выполняется вопреки
    вашим ожиданиям.
  3. Производит ли часть кода эффект, который вы не ожидаете?
    Убедитесь, что вы понимаете этот участок кода; будьте особенно внимательны,
    если код вызывает функции или методы из других модулей Python. Прочтите
    документацию по функциям, которые вы используете. Изучите эти функции, написав
    для них несложные тесты.

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

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

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

Мое длинное выражение не работает¶

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

Пример:

self.hands[i].addCard (self.hands[self.findNeighbor(i)].popCard())

Это выражение можно переписать так:

neighbor = self.findNeighbor (i)
pickedCard = self.hands[neighbor].popCard()
self.hands[i].addCard (pickedCard)

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

Другая проблема, связанная с длинными выражениями, состоит в том, что порядок вычисления выражения может оказаться не таким, какой вы ожидали. Например, если вы запишете выражение x/2pi на Python, у вас может получиться:

Это неправильно. Операции умножения и деления имеют одинаковый приоритет, и их вычисление производится слева направо. Поэтому данное выражение на Python вычисляет (x/2)pi.

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

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

Моя функция (или метод) возвращает не то, что мне нужно¶

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

return self.hands[i].removeMatches()

напишите:

count = self.hands[i].removeMatches()
return count

Теперь у вас есть возможность вывести значение count перед выходом из функции.

Ничего не понимаю, бред какой-то¶

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

  1. Отчаяние и/или ярость.
  2. Вера в сверхъестественное (компьютер меня ненавидит) и магию (программа работает только
    тогда, когда я надеваю шляпу).
  3. Программирование методом случайного тыка (кодирование одного и того же разными способами
    в надежде, что один из способов сработает).

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

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

Нет, мне реально нужна помощь¶

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

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

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

  1. Если имеется сообщение об ошибке, то что это за сообщение и где оно возникает?
  2. Какое последнее изменение вы сделали перед тем, как возникла проблема? Какие
    строки кода вы изменили/добавили, или какой новый тест не проходит?
  3. Что вы уже сделали, и что поняли к настоящему моменту в связи с этой проблемой?

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

И помните, ваша цель не только в том, чтобы сделать работающую программу. Цель еще и в том, чтобы учиться делать работающие программы.

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

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

  1. Синтаксические.
  2. Логические (исключения).

Ошибки синтаксиса

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

Давайте посмотрим на один пример:

>>> if a < 3
  File "<interactive input>", line 1
    if a < 3
           ^
SyntaxError: invalid syntax

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

Мы можем заметить здесь, что в операторе if отсутствует двоеточие.

Логические ошибки (исключения)

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

Например, они возникают, когда мы пытаемся открыть файл (для чтения), который не существует (FileNotFoundError), пытаемся разделить число на ноль (ZeroDivisionError) или импортировать несуществующий модуль (ImportError).

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

Давайте посмотрим, как Python обрабатывает эти ошибки:

>>> 1 / 0
Traceback (most recent call last):
 File "<string>", line 301, in runcode
 File "<interactive input>", line 1, in <module>
ZeroDivisionError: division by zero

>>> open("imaginary.txt")
Traceback (most recent call last):
 File "<string>", line 301, in runcode
 File "<interactive input>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'imaginary.txt'

Встроенные исключения

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

print(dir(locals()['__builtins__']))

locals() [‘__ builtins__’] вернет модуль встроенных исключений, функций и атрибутов. dir позволяет нам перечислять эти атрибуты в виде строк.

Ниже перечислены некоторые из распространенных встроенных исключений в программировании на Python, а также ошибки, вызывающие их:

Исключение Причина ошибки
AssertionError Возникает при сбое утверждения утверждения.
AttributeError Возникает при сбое присвоения атрибута или ссылки.
EOFError Возникает, когда функция input() достигает условия конца файла.
FloatingPointError Возникает при сбое операции с плавающей запятой.
ГенераторВыход Поднимается при вызове метода генератора close().
ImportError Возникает, когда импортированный модуль не найден.
IndexError Возникает, когда индекс последовательности выходит за пределы допустимого диапазона.
KeyError Возникает, когда ключ не найден в словаре.
KeyboardInterrupt Возникает, когда пользователь нажимает клавишу прерывания (Ctrl + C или Delete).
MemoryError Возникает, когда для операции не хватает памяти.
NameError Возникает, когда переменная не найдена в локальной или глобальной области.
NotImplementedError Поднят абстрактными методами.
OSError Возникает, когда работа системы вызывает ошибку, связанную с системой.
OverflowError Возникает, когда результат арифметической операции слишком велик для представления.
ReferenceError Возникает, когда слабый ссылочный прокси используется для доступа к референту со сборкой мусора.
Ошибка выполнения Возникает, когда ошибка не попадает ни в одну другую категорию.
StopIteration Вызывается функцией next(), чтобы указать, что итератор больше не возвращает элемент.
Синтаксическая ошибка Возникает парсером при обнаружении синтаксической ошибки.
IndentationError Возникает при неправильном отступе.
TabError Возникает, когда отступ состоит из несовместимых знаков табуляции и пробелов.
Системная ошибка Возникает, когда интерпретатор обнаруживает внутреннюю ошибку.
SystemExit Вызывается функцией sys.exit().
TypeError Возникает, когда функция или операция применяется к объекту неправильного типа.
UnboundLocalError Возникает, когда делается ссылка на локальную переменную в функции или методе, но с этой переменной не привязано никакого значения.
UnicodeError Возникает при возникновении ошибки кодирования или декодирования, связанной с Unicode.
UnicodeEncodeError Возникает, когда во время кодирования возникает ошибка, связанная с Unicode.
UnicodeDecodeError Возникает, когда во время декодирования возникает ошибка, связанная с Unicode.
UnicodeTranslateError Возникает, когда во время перевода возникает ошибка, связанная с Unicode.
ValueError Возникает, когда функция получает аргумент правильного типа, но неправильное значение.
ZeroDivisionError Возникает, когда второй операнд деления или операции по модулю равен нулю.

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

Встроенные и определяемые пользователем исключения в Python можно обрабатывать с помощью операторов try, except и finally.

47422cookie-checkОшибки и встроенные исключения в Python

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

  • Что такое ошибка исполнения codeforces
  • Что такое ошибка инициализации графического устройства
  • Что такое ошибка исключительной блокировки информационной базы
  • Что такое ошибка инициализации графического движка
  • Что такое ошибка исключения саи

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

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