Ошибки в программах Python: синтаксис, исключения и логика
Обновлено:
При разработке программ мы неизбежно сталкиваемся с ошибками.
На этом уроке мы пока не будем подробно изучать конкретные виды исключений:
TypeError
ValueError
ZeroDivisionError
NameError
...
С ними мы познакомимся позже.
Сейчас наша задача — понять на более общем уровне, какие проблемы могут возникать в программе, чем они отличаются и насколько серьёзными могут быть их последствия.
В этом конспекте будем использовать условную учебную классификацию из четырёх уровней:
- синтаксические ошибки;
- нарушения PEP 8;
- исключения во время выполнения;
- логические ошибки.
Это не официальная классификация Python, а удобный способ расположить проблемы примерно от самых простых к наиболее опасным.
1. Синтаксическая ошибка
Начнём с самой простой ситуации.
Рассмотрим рабочую программу:
first_number = 10
second_number = 5
result = first_number + second_number
print('Программа запущена')
print(result)
print('Программа завершена')
Результат:
Программа запущена
15
Программа завершена
В коде правильный синтаксис, поэтому Python может подготовить программу к выполнению и запустить её.
2. Что такое синтаксис
У языка Python существуют правила написания кода.
Например, строка должна иметь закрывающую кавычку:
message = 'Hello'
У вызова функции должны быть правильно расставлены скобки:
print(message)
После заголовка функции ставится двоеточие:
def get_message():
return 'Hello'
Если нарушить правила языка, Python не сможет правильно разобрать исходный код.
Возникает:
SyntaxError
Синтаксическую ошибку также называют ошибкой разбора:
parsing error
Python обнаруживает такую проблему до начала обычного выполнения кода этого модуля.
3. Незакрытая скобка
Например:
print('Hello'
Python не может правильно разобрать выражение.
При запуске мы получим сообщение примерно такого вида:
File ".../main.py", line 1
print('Hello'
^
SyntaxError: '(' was never closed
Программа не начнёт выполнять эту строку как обычный Python-код.
4. Лишняя скобка
print('Hello'))
Здесь находится лишняя закрывающая скобка.
Python снова обнаружит синтаксическую ошибку:
SyntaxError
5. Незакрытая строка
message = 'Hello
Открывающая кавычка есть, а закрывающей нет.
Python не может определить границу строки.
Результат:
SyntaxError
6. Неправильные кавычки
Например:
message = 'I'm learning Python'
Апостроф в слове I'm преждевременно закрывает строку.
Правильно:
message = "I'm learning Python"
Или:
message = 'I\'m learning Python'
7. Ошибка отступов
В Python отступ является частью синтаксиса.
Правильно:
def get_message():
return 'Hello'
Неправильно:
def get_message():
return 'Hello'
Python сообщит об ошибке отступа.
Обычно мы увидим:
IndentationError
8. IndentationError связан с SyntaxError
Можно проверить наследование:
print(IndentationError.mro())
В цепочке будет:
IndentationError
SyntaxError
Exception
...
То есть IndentationError является более конкретным видом SyntaxError.
Поэтому на этом уроке мы можем объединённо рассматривать их как проблемы синтаксиса программы.
9. Когда возникает синтаксическая ошибка
Упрощённо запуск Python-программы можно представить так:
Исходный код Python
↓
разбор и компиляция
↓
байт-код
↓
выполнение
Если в запускаемом модуле есть синтаксическая ошибка, Python обнаружит её во время разбора или компиляции, то есть до обычного выполнения кода этого модуля.
Поэтому синтаксическая ошибка отличается от исключения, которое появляется уже во время выполнения программы.
10. Ошибка в конце файла
Рассмотрим:
print('Первая строка')
print('Вторая строка')
print('Третья строка')
message = 'Hello
Можно предположить, что первые три print() успеют выполниться.
Но этого не произойдёт.
Python сначала должен успешно разобрать и скомпилировать модуль.
Из-за синтаксической ошибки в последней строке выполнение файла не начинается.
Поэтому мы не увидим:
Первая строка
Вторая строка
Третья строка
11. Как читать сообщение о синтаксической ошибке
Например:
File "/Users/user/project/main.py", line 15
print('Hello'
^
SyntaxError: '(' was never closed
Здесь находится несколько полезных частей.
Путь к файлу
/Users/user/project/main.py
Он показывает, в каком файле обнаружена проблема.
Номер строки
line 15
Показывает строку, возле которой Python обнаружил ошибку.
Фрагмент кода
print('Hello'
Python показывает проблемный участок.
Указатель
^
Он помогает обратить внимание на определённое место выражения.
Название проблемы
SyntaxError
Дополнительная подсказка
'(' was never closed
Python сообщает, что открывающая скобка не была закрыта.
Современные версии Python стараются достаточно подробно подсвечивать диапазон, связанный с синтаксической ошибкой.
12. Указатель не всегда показывает место исправления
Это важный момент.
Python показывает место, где он обнаружил проблему.
Но настоящая причина иногда находится немного раньше.
Например, если забыть закрывающую скобку, Python может окончательно понять, что код неправильный, только на следующей строке.
Поэтому при SyntaxError нужно смотреть:
- на указанную строку;
- на сам указатель;
- иногда на одну или несколько строк выше.
Официальная документация отдельно отмечает, что место обнаружения синтаксической ошибки не обязательно является точным местом, которое нужно исправить.
13. Почему синтаксические ошибки обычно проще всего
В нашей учебной классификации это наиболее простой уровень проблемы.
Причины:
- программа сразу сообщает об ошибке;
- обычно указан файл;
- указан номер строки;
- показан проблемный код;
- часто есть понятная подсказка;
- ошибка воспроизводится при каждом запуске;
- IDE также часто подсвечивает её ещё до запуска.
Поэтому синтаксическую ошибку обычно сравнительно легко найти и исправить.
14. Синтаксическая ошибка в настоящем проекте
Если синтаксическая ошибка находится в модуле, который приложение пытается запустить или импортировать, этот модуль не сможет нормально загрузиться.
В нормальном процессе разработки такие проблемы должны обнаруживаться ещё до публикации:
написание кода
↓
запуск
↓
проверки
↓
тестирование
↓
публикация
Поэтому синтаксические ошибки гораздо реже становятся незаметными проблемами для конечного пользователя, чем ошибки бизнес-логики.
В больших проектах нельзя утверждать, что синтаксический дефект физически никогда не может попасть на сервер: например, он может находиться в ещё не загруженном модуле. Но нормальные проверки и тесты должны обнаруживать такие проблемы до релиза.
Нарушения PEP 8
15. Что такое PEP 8
PEP 8 — руководство по стилю Python-кода.
В нём описаны рекомендации по:
- отступам;
- пробелам;
- пустым строкам;
- именованию;
- длине строк;
- оформлению функций;
- структуре кода.
Главная задача таких правил — делать код понятным и единообразным. PEP 8 прямо подчёркивает важность читаемости и согласованности кода.
16. Нарушение PEP 8 — технически не ошибка Python
Важно правильно использовать терминологию.
Например:
first_number=10
second_number=5
result=first_number+second_number
print(result)
Этот код нарушает привычное оформление PEP 8.
Но Python успешно выполнит программу:
15
Поэтому нарушение PEP 8 — это не синтаксическая ошибка и не исключение.
Точнее называть это:
нарушением стиля
или:
style issue
В нашей учебной классификации мы просто помещаем такие проблемы на второй уровень.
17. Правильное оформление
Лучше:
first_number = 10
second_number = 5
result = first_number + second_number
print(result)
Программа работает так же:
15
Но код стал легче читать.
18. Плохие имена
Такой код может работать:
a = 1500
b = 300
c = a + b
print(c)
Но из названий непонятно, что означают данные.
Лучше:
product_price = 1500
delivery_price = 300
total_price = product_price + delivery_price
print(total_price)
Теперь смысл программы намного понятнее.
19. Плохое оформление функции
Такой код может работать:
def calculate_square_area(side_length):return side_length**2
Но читать его неудобно.
Лучше:
def calculate_square_area(side_length):
return side_length ** 2
Результат программы не изменился.
Изменилась читаемость исходного кода.
20. Почему стиль важен
Предположим, проект разрабатывает команда:
5 разработчиков
Если каждый пишет код совершенно по-разному, другим программистам приходится тратить дополнительное время на понимание чужого кода.
Плохая читаемость влияет на:
- скорость разработки;
- скорость поиска ошибок;
- стоимость поддержки;
- удобство добавления новых функций;
- передачу проекта другому разработчику;
- проведение code review.
Именно поэтому читаемость является одной из важных ценностей Python. Даже «Дзен Python» содержит принцип:
Readability counts.
То есть:
Читаемость имеет значение.
21. Видит ли нарушение PEP 8 пользователь
Обычно конечный пользователь не видит исходный код.
Для него программы:
result=10+5
и:
result = 10 + 5
дают один результат.
Поэтому нарушение стиля само по себе обычно не ломает бизнес-логику.
Но косвенно плохой код может сделать разработку медленнее и повысить вероятность появления настоящих ошибок в будущем.
Исключения во время выполнения
22. Третий уровень
Рассмотрим рабочий синтаксически код:
print('Программа запущена')
result = 10 / 0
print('Программа завершена')
В отличие от предыдущего случая, Python может разобрать этот код.
С точки зрения синтаксиса:
10 / 0
записано правильно.
Программа начинает выполняться.
23. Что произойдёт
Сначала выполняется:
print('Программа запущена')
Мы увидим:
Программа запущена
Затем Python доходит до:
10 / 0
Во время выполнения возникает исключение.
Если оно не обработано, обычное выполнение программы прерывается. Официальная документация отличает такие исключения от синтаксических ошибок именно тем, что синтаксически корректное выражение может вызвать ошибку уже при выполнении.
Последний:
print('Программа завершена')
уже не выполнится.
24. Ошибка в конце программы
print('Старт')
first_number = 10
second_number = 5
print(first_number + second_number)
print('Почти закончили')
result = 10 / 0
До проблемной строки программа работает:
Старт
15
Почти закончили
После этого возникает исключение.
25. Ошибка в середине программы
print('Старт')
first_number = 10
second_number = 5
print(first_number + second_number)
result = 10 / 0
print('Вторая часть программы')
print('Конец')
Будет выполнено:
Старт
15
Затем программа остановится.
Строки:
Вторая часть программы
Конец
не появятся.
26. Ошибка в начале программы
print('Старт')
result = 10 / 0
print('Расчёты')
print('Конец')
Вывод:
Старт
После возникновения необработанного исключения последующий код не выполняется.
27. Ещё несколько примеров
Такой код синтаксически правильный:
number = 10
result = number / 0
Но проблема возникает во время выполнения.
Другой пример:
number = 10
result = number + '5'
Python может прочитать и запустить программу, но при попытке выполнить конкретную операцию возникает исключение.
Ещё пример:
result = len(100)
Синтаксис опять правильный.
Проблема обнаруживается только во время выполнения операции.
Названия конкретных исключений пока запоминать не нужно.
28. Исключение и traceback — не одно и то же
Важно различать два понятия.
Exception
исключение
Это сама исключительная ситуация, возникшая во время выполнения программы.
Traceback
трассировка
Это информация о пути выполнения программы, которая помогает понять, где возникло исключение и как программа пришла к этому месту.
Поэтому технически правильнее говорить:
В программе возникло исключение, и Python показал traceback.
А не:
Произошёл traceback.
Модуль traceback в стандартной библиотеке Python как раз предназначен для работы с такими трассировками.
29. Простой traceback
Рассмотрим:
print('Начало программы')
result = 10 / 0
print('Конец программы')
При запуске мы увидим сообщение примерно такого вида:
Начало программы
Traceback (most recent call last):
File "/Users/user/project/main.py", line 3, in <module>
result = 10 / 0
~~~^~~
ZeroDivisionError: division by zero
Название конкретного исключения пока запоминать не требуется.
Нас интересует структура сообщения.
30. Как читать traceback
Начало:
Traceback (most recent call last):
говорит, что Python показывает трассировку возникшего исключения.
Дальше:
File "/Users/user/project/main.py"
указывается файл.
Затем:
line 3
номер строки.
Затем:
in <module>
показывается контекст выполнения.
Позже, когда программа будет состоять из множества вызывающих друг друга функций, здесь можно будет увидеть имена функций и несколько уровней вызовов.
31. Проблемная строка
Python показывает код:
result = 10 / 0
и может дополнительно подсветить часть выражения:
~~~^~~
Это помогает понять, какое именно действие стало причиной исключения.
32. Последняя строка traceback
В конце находится самая важная краткая информация:
ZeroDivisionError: division by zero
Она состоит из:
название исключения : описание проблемы
На этом уроке конкретные названия исключений не изучаем.
Сейчас достаточно понимать:
Последняя строка обычно даёт главное краткое описание возникшей проблемы.
33. С чего начинать чтение traceback
Для простого traceback удобно действовать так:
- посмотреть последнюю строку;
- понять общее описание проблемы;
- найти свой файл;
- посмотреть номер строки;
- посмотреть саму проблемную операцию.
Пока наши программы небольшие, traceback тоже обычно небольшой.
Позже мы познакомимся со случаями, когда одна функция вызывает другую, та вызывает третью и traceback состоит из большого количества уровней.
34. Почему исключения опаснее
Исключение может возникнуть уже после публикации программы.
Например, приложение было протестировано на обычных данных:
10
20
50
100
Но пользователь каким-то способом передал значение, которое разработчик не предусмотрел.
Программа дошла до редкого сценария и упала.
Это уже непосредственно влияет на пользователя.
35. Редкие сценарии
Представим большой продукт.
Пользователь может:
- вводить разные данные;
- выбирать разные настройки;
- использовать разные комбинации действий;
- работать с разными файлами;
- получать разные данные от внешних сервисов.
Количество возможных сценариев становится огромным.
Разработчики стараются тестировать программу, но некоторые редкие комбинации могут остаться незамеченными.
Именно в таком сценарии может возникнуть исключение, которое не проявлялось во время обычного тестирования.
36. Последствия для пользователя
Например, пользователь нажимает кнопку расчёта.
Он ожидает:
Результат: 1500
Но операция завершается исключением.
В зависимости от приложения пользователь может увидеть:
- сообщение об ошибке;
- пустую страницу;
- неработающую функцию;
- техническое сообщение;
- код ответа сервера;
- неожиданное завершение программы.
Такой дефект уже способен ухудшить впечатление от продукта и привести к потере пользователей.
37. Почему исключение бывает сложно найти
В маленьком учебном файле:
result = 10 / 0
причина очевидна.
В коммерческом приложении ситуация может быть значительно сложнее:
пользователь
↓
одна функция
↓
другая функция
↓
сторонняя библиотека
↓
ещё одна функция
↓
исключение
В traceback тогда появляется стек вызовов.
Чем больше программа, тем больше информации приходится анализировать.
Кроме того, проблема может появляться только при определённом наборе данных.
Логические ошибки
38. Четвёртый уровень
Самая неприятная категория в нашей учебной классификации — логические ошибки.
При логической ошибке:
- синтаксис правильный;
- программа запускается;
- исключения может не быть;
- программа продолжает работать;
- но результат оказывается неправильным.
Проблема находится в самой логике программы.
39. Простая логическая ошибка
Нужно найти периметр прямоугольника:
P = (length + width) × 2
Правильная программа:
length = 10
width = 5
perimeter = (length + width) * 2
print(perimeter)
Результат:
30
40. Неправильная формула
Программист случайно написал:
length = 10
width = 5
perimeter = length + width * 2
print(perimeter)
Результат:
20
Python не покажет никакой ошибки.
Код:
length + width * 2
абсолютно корректен.
Python правильно соблюдает приоритет операторов:
width * 2
а затем выполняет сложение.
Ошибка находится в мысли программиста.
Он написал неправильный алгоритм.
41. Ошибка в расчёте стоимости
Предположим:
product_price = 2000
delivery_price = 300
Клиент должен заплатить:
2300
Правильно:
total_price = product_price + delivery_price
Но программист случайно написал:
total_price = product_price - delivery_price
Результат:
1700
Никакого:
SyntaxError
нет.
Никакого необработанного исключения тоже нет.
Программа просто рассчитала неправильную сумму.
42. Логическая ошибка с float
Мы уже видели:
balance = 0.1 + 0.2
required_amount = 0.3
payment_is_allowed = balance == required_amount
print(payment_is_allowed)
Результат:
False
Python работает правильно.
Проблема в алгоритме программиста: для результата вычислений с float было выбрано неподходящее прямое сравнение.
Если бизнес-логика зависит от такого сравнения, пользователь может получить неправильный результат.
43. Почему логическая ошибка особенно опасна
При синтаксической ошибке Python сразу сообщает:
Здесь проблема.
При необработанном исключении Python также сообщает:
Во время выполнения произошло исключение.
При логической ошибке программа может сообщить:
Всё хорошо.
Хотя результат неправильный.
Именно поэтому такую проблему можно долго не замечать.
44. Пользователь может обнаружить ошибку раньше разработчика
Например, интернет-магазин показывает:
Цена товара: 2000 руб.
Доставка: 300 руб.
Итого: 1700 руб.
Программа работает.
Страница открывается.
Никаких traceback нет.
Но пользователь сразу понимает, что расчёт неправильный.
Разработчик может вообще не знать о проблеме, пока:
- пользователь не пожалуется;
- тест не обнаружит ошибку;
- аналитика не покажет странные данные;
- сотрудник компании не заметит проблему.
45. Ошибочные данные
Логическая ошибка может быть особенно опасной, если программа сохраняет рассчитанные значения.
Представим систему аналитики.
Она каждый день сохраняет:
количество пользователей
выручку
конверсию
средний чек
расходы
Если формула конверсии написана неправильно, система может месяцами сохранять неправильные значения.
Через год компания попытается провести анализ.
И выяснится, что исторические данные были рассчитаны неверно.
Исправить программу сегодня относительно просто.
Исправить год уже накопленных неправильных данных может оказаться намного сложнее или вообще невозможно, если исходные данные не сохранились.
46. Финансовые последствия
Ещё один пример:
product_price = 10_000
discount_percent = 10
Правильная скидка:
discount = product_price * discount_percent / 100
Результат:
1000
Но программист написал:
discount = product_price * discount_percent
Результат:
100000
Программа технически работает.
Все операции допустимы.
Но бизнес-логика полностью неправильная.
В настоящем продукте подобная ошибка может непосредственно привести к финансовым потерям.
47. Почему логическую ошибку сложно искать
При логической ошибке нет обязательной строки:
Error
Нет обязательного traceback.
Нет номера строки, который сразу показывает:
Исправь здесь.
Разработчику приходится:
- воспроизвести неправильный результат;
- понять, какие данные использовались;
- пройти по алгоритму;
- проверить промежуточные расчёты;
- найти место, где логика начинает расходиться с ожидаемой;
- исправить код;
- проверить другие сценарии.
В большой бизнес-логике это может занимать значительно больше времени, чем исправление обычной синтаксической ошибки.
48. Тестирование помогает находить логические ошибки
Если мы знаем правильный результат:
Длина: 10
Ширина: 5
Периметр: 30
мы можем проверить функцию:
def calculate_rectangle_perimeter(length, width):
return (length + width) * 2
result = calculate_rectangle_perimeter(10, 5)
print(result == 30)
Результат:
True
Если программист случайно испортит формулу:
def calculate_rectangle_perimeter(length, width):
return length + width * 2
проверка даст:
False
Позже мы подробно познакомимся с автоматическими тестами.
Одна из их важнейших задач — обнаруживать ситуации, когда программа выполняется, но выдаёт неправильный результат.
Сравнение четырёх уровней
49. Синтаксическая ошибка
Пример:
print('Hello'
Что происходит:
Python не может нормально разобрать/скомпилировать модуль
Программа:
не начинает обычное выполнение этого файла
Обычно:
легко заметить
легко воспроизвести
сравнительно легко исправить
50. Нарушение PEP 8
Пример:
result=10+5
Что происходит:
программа работает
Результат:
правильный
Проблема:
код хуже читается и поддерживается
Технически это не ошибка Python, а проблема стиля.
51. Необработанное исключение
Пример:
result = 10 / 0
Что происходит:
программа запускается
код выполняется до проблемной операции
возникает исключение
Python показывает traceback
обычное выполнение прерывается
Проблема может проявиться уже у конечного пользователя.
52. Логическая ошибка
Пример:
perimeter = length + width * 2
вместо:
perimeter = (length + width) * 2
Что происходит:
программа запускается
исключения нет
программа работает
результат неправильный
Именно поэтому такая проблема может долго оставаться незамеченной.
53. Сравнительная таблица
| Уровень | Пример | Программа запускается | Может дать неправильный результат | Легко обнаружить |
|---|---|---|---|---|
| Синтаксическая ошибка | незакрытая скобка | Нет | — | Обычно да |
| Нарушение PEP 8 | плохое оформление | Да | Обычно нет | Обычно да |
| Необработанное исключение | недопустимая операция | Да, до ошибки | Может | Обычно заметно, но причина бывает сложной |
| Логическая ошибка | неправильная формула | Да | Да | Иногда очень сложно |
54. Важное различие
Официально Python прежде всего различает:
синтаксические ошибки
и:
исключения
А наша расширенная схема нужна для понимания практической разработки:
1. SyntaxError / IndentationError
2. проблемы стиля и PEP 8
3. исключения во время выполнения
4. ошибки логики программы
То есть мы классифицируем проблемы не только по устройству Python, но и по их последствиям для разработчиков и пользователей.
55. Как действовать при проблеме
Если программа вообще не запускается и показывает:
SyntaxError
сначала проверяем синтаксис.
Если программа работает, но IDE или проверка стиля ругается на оформление — проверяем PEP 8.
Если программа выполняется до определённого момента, а затем появляется:
Traceback (most recent call last):
изучаем исключение и traceback.
Если программа работает без сообщений об ошибках, но результат неправильный — ищем ошибку в логике программы.
56. Основные правила урока
Синтаксическая ошибка
Возникает при нарушении синтаксиса Python:
print('Hello'
Она обнаруживается до обычного выполнения кода модуля.
IndentationError является специальным видом SyntaxError.
PEP 8
Нарушение PEP 8:
number=10
обычно не мешает Python выполнить программу.
Это не ошибка выполнения, а проблема качества и читаемости кода.
Исключение
Синтаксически правильная программа может столкнуться с проблемой уже во время выполнения:
10 / 0
До проблемной операции программа выполняется.
Если исключение не обработано, обычное выполнение прерывается.
Traceback
Traceback — это не разновидность ошибки.
Это трассировка, которая помогает понять:
- в каком файле возникло исключение;
- в какой строке;
- в каком контексте;
- через какие вызовы программа пришла к этой точке;
- какое исключение возникло.
Логическая ошибка
Программа может:
успешно запускаться
не показывать исключений
продолжать работать
и при этом выдавать неправильный результат.
Такие ошибки находятся в алгоритме или бизнес-логике программы и часто являются наиболее опасными.
Главная мысль:
Отсутствие SyntaxError и traceback ещё не означает,
что программа работает правильно.