Отладка программ
Цель: научиться исправлять синтаксические и семантические ошибки в программах.
Пример
Синтаксические ошибки
Условие задачи звучит следующим образом:
Выведите на экран значения функции
Решение задачи выглядит следующим образом:
# импортируем функцию для вычисления корня
from math import sqrt
# определение функции
def func(x):
return sqrt(x - 5)
# запрашиваем ввод необходимых данных
xn = float(input("Введите начальное значение x "))
xk = float(input("Введите конечное значение x ")
step = float(input("Введите шаг изменения x "))
print("Значения функции:)
x = nx # начальное значение x
while x < xk
y = func(x) # вычисляем y
print(f"{x:.2f} {y:.2f}")
x = x + 1
Скачайте файл с решением ex01.py и откройте его в среде разработки.
Чтобы сохранить этот файл, щёлкните по ссылке правой кнопкой мыши, и выберите пункт меню Сохранить объект как….
Запустите программу. Python сообщит о наличии ошибки:
File "ex01.py", line 11
step = float(input("Введите шаг изменения x "))
^
SyntaxError: invalid syntax
Зачастую, стоит искать синтаксическую ошибку выше той строки, что указана в сообщении об ошибке.
В 10 строке не хватает закрывающей скобки. Добавьте её и запустите программу повторно.
Следующая ошибка:
File "ex01.py", line 13
print("Значения функции:)
^
SyntaxError: EOL while scanning string literal
EOL while scanning string literal
означает, что, скорее всего, проблема в нехватке кавычек в написании строки. Добавьте недостающую кавычку в 13 строке. Запустите программу ещё раз.
File "ex01.py", line 16
while x < xk
^
SyntaxError: invalid syntax
Очередная ошибка. Вспоминаем, что блочные инструкции должны заканчиваться символов двоеточия :
, показывая, где начинается вложенный набор команд. Добавьте недостающее двоеточие, и запустите программу.
Синтаксических ошибок больше нет, и программа предлагает ввести необходимые данные.
Ошибки времени выполнения
Введите для тестирования следующие данные:
Введите начальное значение x 2
Введите конечное значение x 10
Введите шаг изменения x 1
Возникнет runtime-ошибка (ошибка времени выполнения):
Traceback (most recent call last):
File "ex01.py", line 15, in <module>
x = nx # начальное значение x
NameError: name 'nx' is not defined
Ошибка NameError
означает, что используется переменная, которой не присвоено значение. Также всё может объясняться опечаткой в имени переменной. В 15 строке исправьте имя nx
на xn
. Запустите программу и повторите ввод данных.
Traceback (most recent call last):
File "ex01.py", line 17, in <module>
y = func(x) # вычисляем y
File "ex01.py", line 6, in func
return sqrt(x - 5)
ValueError: math domain error
Следующее сообщение длиннее предыдущих, так как проблема связана с вычислениями происходящими в функции. Программа (модуль) вызвала функцию func
в 17 строке. Интерпретатор начал выполнение функции, и произошла ошибка (строка 6). Поэтому сообщение об ошибке содержит ссылку на две строки - где в функции произошла исключительная ситуация, а также где в программе была вызвана функция, выполнение которой привело к исключительной ситуации. Достаточно исправить ошибку в функции (6 строка). Поэтому сообщения об ошибке следует читать снизу вверх.
Есть два варианта исправить данную ошибку - использовать обработку исключений (try...except
) или условный оператор (if
). Оба варианта приведены ниже:
# исправление с помощью if
def func(x):
# если x меньше 5, возвращаем 0
if x < 5:
return 0
return sqrt(x - 5)
# исправление с помощью try..except
def func(x):
try:
return sqrt(x - 5)
except ValueError:
# если возникает ошибка, вернём 0
return 0
В данном случае, первый вариант лучше. Но в некоторых ситуациях, выгоднее сначала попытаться выполнить команду, и, в случае возникновения ошибки, использовать запасной план. Такой подход в программировании характеризует следующий слоган: Проще просить прощения, чем просить разрешения (применяйте такой подход только в программировании, а не в реальной жизни 🎓).
Семантические ошибки
Запустите программу со следующими входными данными:
Введите начальное значение x 2
Введите конечное значение x 10
Введите шаг изменения x 2
Результат работы программы будет выглядеть следующим образом:
Значения функции:
2.00 0.00
3.00 0.00
4.00 0.00
5.00 0.00
6.00 1.00
7.00 1.41
8.00 1.73
9.00 2.00
С клавиатуры был введён шаг равный двум, при этом значение x
внутри цикла всегда меняется на единицу. Очевидно, что ошибка связана с изменением переменной цикла:
Несмотря на простоту данной смысловой ошибки, в качестве тренировки, воспользуемся модулем logging
. В простых программах для вывода промежуточных результатов вычислений, можно воспользоваться функцией print
.
В начале программы добавьте импорт модуля logging
и его настройку:
Добавим вывод сообщений перед циклом, внутри цикла (вывод переменной цикла), и после цикла:
logging.debug("Цикл начинается") # добавьте эту строку
while x < xk:
y = func(x) # вычисляем y
print(f"{x:.2f} {y:.2f}")
x = x + 1
logging.debug(f"x={str(x)}") # добавьте эту строку
logging.debug("Цикл завершился") # добавьте эту строку
Запустите программу с произвольными данными, и шагом отличающимся от 1. Сообщения модуля logging
могут выглядеть следующим образом:
DEBUG:root:Цикл начинается
DEBUG:root:x=3.0
DEBUG:root:x=4.0
DEBUG:root:x=5.0
DEBUG:root:x=6.0
DEBUG:root:x=7.0
DEBUG:root:x=8.0
DEBUG:root:x=9.0
DEBUG:root:x=10.0
DEBUG:root:Цикл завершился
Шаг изменения цикла всегда меняется на 1, несмотря на то, что для хранения шага используется переменная step
. Исправьте изменение шага следующим образом:
Теперь шаг будет учитываться в работе цикла.
После исправления ошибки журналирование можно отключить. Для этого добавьте в начале программы следующую строку:
Журналирование полезно в крупных программах, особенно если они остаются запущенными длительное время, а логи их работы записываются в файлы.
Теперь программа работает почти правильно, но осталась ещё одна семантическая ошибка. Найдите и исправьте её.
Задания для самостоятельной работы
Скачайте архив, доступный по данной ссылке.
Чтобы сохранить этот файл, щёлкните по ссылке правой кнопкой мыши, и выберите пункт меню Сохранить объект как….
Распакуйте архив, и откройте папку debug. Не удаляйте архив после распаковки. В данной папке располагаются 11 файлов с решениями задач, которые содержат ошибки различного типа. Условия задач указаны в начале каждого файла.
Последовательно открывайте файлы и, применяя различные практики отладки, исправьте ошибки в исходном коде программ.
Если вы считаете, что лучше начать отладку какой-либо программы сначала, возьмите исходную версия задания из архива.
Рекомендации по отладке программ
Список ошибок
Имя ошибки | Пояснение |
---|---|
SyntaxError | Ошибка в синтаксисе: пропущен необходимый символ, не хватает открывающей или закрывающей скобки и т.д. |
NameError | В выражении используется переменная, которой ещё не было присвоено значение. Возможна опечатка в имени переменной, функции, класса. |
ValueError | Функция ожидала аргумент другого типа. Например в функцию int передана строка, которая не содержит целое число. |
TypeError | В выражении используются несовместимые типы. Например попытка сложить строку и число. Или в функцию передано неправильное количество аргументов. Для проверки типа переменной можно использовать функцию type() . |
KeyError | Попытка получить значение по ключу, которого нет в словаре. |
IndexError | Попытка получить элемент из списка, или символ из строки по индексу, которого нет в последовательности. |
Исправление синтаксических ошибок
- Убедитесь, что для имён переменных не используются служебные слова языка.
- В начале нового блока расположено двоеточие (после
for
,while
,if
,def
,class
). - Соблюдены правила использования отступов.
- Строки обладают парными кавычками.
- Для каждой открывающей скобки есть своя закрывающая –
()
,{}
,[]
.
Исправление ошибок времени выполнения
Программа ничего не выводит на экран
Убедитесь, что описанные в программе функции или методы классов вызываются, для получения результата и вывода их на экран с помощью print
.
Программа выполняется без остановки
Это может произойти за-за бесконечного цикла. Чтобы найти цикл, который не завершается, разместите перед каждым циклом в программе print
c сообщением “Начало цикла N”, а после цикла - “Конец цикла N”, где N - номер цикла по-порядку. Таким образом можно определить, какой из циклов в программе выполняется бесконечно:
Когда бесконечный цикл найден, разместите в конце цикла print
с выводом переменной цикла и условия завершения цикла (для while
):
В примере выше получим следующее:
...
1 True
1 True
1 True
...
Исходя из вывода становится понятно, что условие завершения цикла никогда не становится ложным, так как не изменяется значение переменной x
. Исправленная программа выглядит следующим образом:
В программе слишком много функций print
для отладки
Для отладочных сообщений в больших программах удобнее использовать модуль журналирования logging
:
import logging
logging.basicConfig(level=logging.DEBUG)
# разкоментировать, чтобы отключить отладочные сообщения
# logging.disable()
logging.debug("Начало цикла")
x = 1
while x < 10:
y = x * x
logging.debug(x, x < 10)
logging.debug("Конец цикла")
Используя данный модуль, можно отключить все отладочные сообщения вызовом одной функции logging.disable()
.
Выражение слишком большое, чтобы понять где ошибка
Длинное выражение можно разбить на несколько меньших выражений, чтобы точнее определить источник ошибки.
goods = {
"cars": {"BMW": "1000", "Audi": "900", "Honda": "300"},
"bikes": []
}
# сложная для чтения строка
audi_price = int(goods["cars"].get("Audi", "0"))
При возникновении ошибки в последней строке, сложно определить конкретную операцию, которая её вызвала. В таком случае сложною строку лучше разбить на ряд простых действий:
Функция или метод класса возвращают неправильное значение
Если функция или метод содержат return
со сложным выражением, то его не получится вывести на экран до возврата значения. Например вместо:
можно записать:
Таким образом, можно вывести возвращаемое значение на экран, до завершения функции.