Отличный помощник для отладки

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

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

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

Что такое трассировка в Python?

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

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

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

Давайте рассмотрим простой пример.

Фрагмент кода ниже создает функцию, которая складывает два числа и умножает сумму на первое число. Затем она вызывает функцию с аргументами 5 и 4. Однако 4 передается как строка, поэтому на самом деле это не число.

def add_and_multiply(x, y):
    return (x + y) * x

add_and_multiply(5, "4")

Когда этот код выполняется, Python вызывает следующее исключение:

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

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

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

Например, когда я сохраняю приведенный выше фрагмент кода как «sample_script.py» и пытаюсь запустить его в терминале, я получаю следующую трассировку:

Traceback (most recent call last):
  File "/Users/sonery/sample_script.py", line 6, in <module>
    add_and_multiply(5, "6")
  File "/Users/sonery/sample_script.py", line 2, in add_and_multiply
    print((x + y) * x)
TypeError: unsupported operand type(s) for +: 'int' and 'str'

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

Общие типы трассировки

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

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

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

Давайте рассмотрим некоторые из часто встречающихся типов ошибок в сообщениях трассировки.

Ошибка типа

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

Атрибутеррор

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

Классы могут иметь атрибуты данных и процедурные атрибуты (т.е. методы):

  • Атрибуты данных: что необходимо для создания экземпляра класса
  • Методы (то есть процедурные атрибуты): как мы взаимодействуем с экземплярами класса.

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

Вот пример:

mylist = [1, 2, 3, 4, 5]

mylist.add(10)

# output
Traceback (most recent call last):
  File "<file>", line 3378, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-25-4ad0ec665b52>", line 3, in <module>
    mylist.add(10)
AttributeError: 'list' object has no attribute 'add'

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

ImportError и ModuleNotFoundError

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

Чтобы использовать такие библиотеки, а также встроенные библиотеки Python (например, os, запросы), нам нужно их импортировать. Если при их импорте возникает проблема, возникает ошибка импорта или исключение ошибки «модуль не найден».

Например, в следующем фрагменте кода мы пытаемся импортировать класс логистической регрессии из Scikit-learn.

from sklearn import LogisticRegression

# output
Traceback (most recent call last):
  File "<file>", line 3378, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-22-b74afc1ba453>", line 1, in <module>
    from sklearn import LogisticRegression
ImportError: cannot import name 'LogisticRegression' from 'sklearn' (/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/sklearn/__init__.py)

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

from sklearn.linear_model import LogisticRegression

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

import openpyxl

# output
Traceback (most recent call last):
  File "<file>", line 3378, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-23-f5ea1cbb6934>", line 1, in <module>
    import openpyxl
  File "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pydev/_pydev_bundle/pydev_import_hook.py", line 21, in do_import
    module = self._system_import(name, *args, **kwargs)
ModuleNotFoundError: No module named 'openpyxl'

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

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

names = ["John", "Jane", "Max", "Emily"]

# Get the third item
names[2]

# output
"Max"

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

names = ["John", "Jane", "Max", "Emily"]

# Get the sixth item
names[5]

# output
Traceback (most recent call last):
  File "<file>", line 3378, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-30-3033b2837dcd>", line 3, in <module>
    names[5]
IndexError: list index out of range

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

Давайте сделаем еще один пример, используя Pandas DataFrame.

import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.randint(10, size=(5, 2)), columns=["A", "B"])

df

# output
   A  B
0  1  6
1  6  3
2  8  8
3  3  5
4  5  6

Переменная df представляет собой DataFrame с 5 строками и 2 столбцами. Следующая строка кода пытается получить значение в третьем столбце первой строки.

df.iloc[0, 3]

# output

Traceback (most recent call last):
  File "<file>", line 3378, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<file>", line 1, in <module>
    df.iloc[0, 3]
  File "<file>", line 960, in __getitem__
    return self.obj._get_value(*key, takeable=self._takeable)
  File "<file>", line 3612, in _get_value
    series = self._ixs(col, axis=1)
  File "<file>", line 3439, in _ixs
    label = self.columns[i]
  File "<file>", line 5039, in __getitem__
    return getitem(key)
IndexError: index 3 is out of bounds for axis 0 with size 2

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

ИмяОррор

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

Вот пример:

members = ["John", "Jane", "Max", "Emily"]

member[0]

# output
Traceback (most recent call last):
  File "<file>", line 3378, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-35-9fcefb83a26f>", line 3, in <module>
    name[5]
NameError: name 'member' is not defined

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

ValueError

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

import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.randint(10, size=(5, 2)), columns=["A", "B"])

df

# output
   A  B
0  1  6
1  6  3
2  8  8
3  3  5
4  5  6

Допустим, мы хотим добавить новый столбец в этот DataFrame.

df["C"] = [1, 2, 3, 4]

# output
Traceback (most recent call last):
  File "<file>", line 3378, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<file>", line 1, in <module>
    df["C"] = [1, 2, 3, 4]
  File "<file>", line 3655, in __setitem__
    self._set_item(key, value)
  File "<file>", line 3832, in _set_item
    value = self._sanitize_column(value)
  File "<file>", line 4535, in _sanitize_column
    com.require_length_match(value, self.index)
  File "<file>", line 557, in require_length_match
    raise ValueError(
ValueError: Length of values (4) does not match length of index (5)

Как объясняется в сообщении об ошибке, DataFrame имеет 5 строк, поэтому каждый столбец имеет 5 значений. Когда мы пытаемся создать новый столбец со списком из 4 элементов, мы получаем ошибку значения.

Заключение

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

В этой статье мы узнали, что такое трассировка, как ее читать и некоторые распространенные типы трассировки.

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

Спасибо за чтение. Пожалуйста, дайте мне знать, если у вас есть какие-либо отзывы.