Ошибки и отладка: как читать сообщения и находить причину
Давайте начнём с признания, которое многим экономит месяцы страданий.
Профессиональный программист видит ошибки постоянно. Не иногда, не в плохой день — постоянно. Человек с пятнадцатью годами опыта за час работы получит десяток сообщений об ошибках. Разница между ним и новичком не в том, что у него ничего не ломается. Разница в том, что он тратит на каждую поломку двадцать секунд, а не двадцать минут паники.
Красный текст в консоли — это не оценка вашего интеллекта и не наказание. Это сообщение от программы, которая пытается вам помочь. Она честно говорит: «я дошла вот до этого места, и вот что мне помешало». Python в этом смысле один из самых вежливых языков: он объясняет проблему почти человеческими словами.
Задача этой статьи — превратить «ой, что-то красное» в «ага, понял, сейчас поправлю».
Три вида ошибок
Ошибки бывают трёх сортов, и различать их важно, потому что ищут их по-разному.
и нажали «запустить»"] --> B{"Python понимает
текст программы?"} B -- "нет" --> C["Синтаксическая ошибка
SyntaxError, IndentationError
Программа НЕ запустилась вообще"] B -- "да" --> D["Программа пошла выполняться"] D --> E{"Всё ли получилось
по дороге?"} E -- "нет" --> F["Ошибка времени выполнения
NameError, TypeError, ...
Программа упала на полпути"] E -- "да" --> G{"Результат
правильный?"} G -- "нет" --> H["Логическая ошибка
Никто ничего не сказал.
Программа спокойно врёт"] G -- "да" --> I["Всё хорошо"]
Синтаксическая ошибка — вы написали то, что не является программой на Python. Как если бы в письме вы написали «Здравствуйте уважаем))(ый». Python даже не пытается это выполнять — он не смог прочитать. Хорошая новость: такие ошибки находятся мгновенно и почти всегда чинятся за секунды.
Ошибка времени выполнения (runtime error) — текст программы правильный, но во время работы случилось невозможное: сложили число со словом, поделили на ноль, попросили десятый элемент у списка из трёх. Программа останавливается и печатает traceback — о нём главный разговор впереди.
Логическая ошибка — самая коварная. Программа запустилась, отработала, ничего не сказала — и выдала неправильный ответ. Калькулятор зарплаты вместо 50 000 показал 5 000. Никто не подсветит вам строку: с точки зрения Python всё безупречно, вы просто попросили не то, что хотели. Именно на такие ошибки уходит львиная доля времени отладки.
Аналогия. Представьте, что вы пишете рецепт для человека, который выполняет его буквально.
- Синтаксическая ошибка: «возьмите 200 грмм мкуи и» — читающий останавливается на первом слове, которого не понимает.
- Ошибка времени выполнения: «достаньте яйца из холодильника» — а холодильник пустой. Дошёл до шага и встал.
- Логическая ошибка: вы написали «200 грамм соли» вместо «200 грамм сахара». Всё выполнимо, всё выполнено, торт несъедобен.
Где аналогия ломается. Человек, готовящий по рецепту, скорее всего заметит странность с солью и переспросит. Компьютер не переспрашивает никогда. У него нет здравого смысла и представления о том, что вы «наверное имели в виду». Это одновременно его главный недостаток и главное достоинство: он абсолютно предсказуем.
Синтаксические ошибки: программа даже не стартовала
Начнём с простого. Запустите такой код:
print("привет"
print("пока")
Вы увидите примерно это:
File "main.py", line 2
print("пока")
^^^^^
SyntaxError: '(' was never closed
Читаем дословно:
File "main.py", line 2— файл и номер строки, на которой Python сдался.print("пока")— сама строка, чтобы вы её видели.^^^^^— стрелочки под подозрительным местом.SyntaxError— «синтаксическая ошибка», то есть «я не понял текст».'(' was never closed— «круглая скобка так и не была закрыта».
И вот важнейший практический совет, который сэкономит вам часы: при синтаксической ошибке смотрите не только на указанную строку, но и на предыдущую. Python читает текст слева направо и сверху вниз; он замечает беду только тогда, когда становится совсем непонятно. Скобку забыли в первой строке, а пожаловался он на вторую — потому что до второй строки он честно ждал, что скобка вот-вот закроется.
Самые частые синтаксические ошибки у новичков:
| Что вы написали | Что скажет Python | Что на самом деле |
|---|---|---|
if x = 5: |
SyntaxError: invalid syntax. Maybe you meant '==' ... |
= — присвоить, == — сравнить |
if x > 5 (без двоеточия) |
SyntaxError: expected ':' |
«ожидалось двоеточие» |
print("привет) |
SyntaxError: unterminated string literal |
«незавершённая строка» — нет второй кавычки |
| Пробелы в начале строки без причины | IndentationError: unexpected indent |
«неожиданный отступ» |
Тело if не сдвинуто вправо |
IndentationError: expected an indented block |
«ожидался блок с отступом» |
| Смешали пробелы и табы | TabError: inconsistent use of tabs and spaces |
«непоследовательное использование табов и пробелов» |
Про отступы мы говорили в статье Условия — в Python они не украшение, а часть синтаксиса. Настройте редактор так, чтобы Tab вставлял четыре пробела, и TabError вы больше не увидите никогда.
Traceback: главный инструмент, который все игнорируют
Теперь про ошибки времени выполнения. Когда программа падает на ходу, Python печатает traceback — буквально «обратный след». Это не «сообщение об ошибке», это целый отчёт о том, как программа сюда попала.
Новички смотрят на traceback как на стену текста и в лучшем случае выхватывают оттуда одно слово. А там всё разложено по полочкам.
Правило чтения одно: читайте снизу вверх.
- Самая последняя строка — тип ошибки и пояснение. Это ответ на вопрос «что случилось».
- Предпоследний блок (файл, строка, функция) — ответ на вопрос «где случилось». Это почти всегда ваш код и почти всегда именно та строка, которую надо чинить.
- Всё остальное выше — «как мы сюда попали»: кто какую функцию вызвал. Пригодится, когда ошибка глубоко внутри.
Соберём это на живом примере. Создайте файл и запустите:
def average(numbers):
"""Считает среднее арифметическое списка чисел."""
total = 0
for n in numbers: # проходим по всем числам
total = total + n # накапливаем сумму
return total / len(numbers) # делим сумму на количество
scores = [] # список пустой — вот она, будущая беда
print(average(scores))
Результат:
Traceback (most recent call last):
File "main.py", line 9, in <module>
print(average(scores))
~~~~~~~^^^^^^^^
File "main.py", line 6, in average
return total / len(numbers)
~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zero
Разбираем по правилу:
- Последняя строка:
ZeroDivisionError: division by zero— «ошибка деления на ноль: деление на ноль». Что случилось — ясно. - Предпоследний блок:
File "main.py", line 6, in average— где: строка 6, внутри функцииaverage. Волнистая линия~~~~~~^~~~подсказывает, какая именно часть выражения виновата. - Выше:
line 9, in <module>— как попали: строка 9 вызвалаaverage. Слово<module>означает «код на верхнем уровне файла, не внутри функции».
Вывод: len(numbers) вернул 0, потому что список пустой. Чинить надо не строку 6, а логику: либо не звать average с пустым списком, либо научить функцию с этим справляться.
Фраза most recent call last в первой строке — это подсказка от самого Python: «самый свежий вызов — последний». То есть внизу. То есть читайте снизу.
Словарь типичных ошибок Python
Вот список того, с чем вы столкнётесь в первые месяцы. Держите его под рукой — 90% ваших падений будут отсюда.
NameError: name 'x' is not defined
Дословно: «имя x не определено». Python дошёл до слова x и не нашёл, что оно значит.
massage = "привет"
print(message) # опечатка: massage vs message
Причины по частоте: опечатка в имени (самая частая), забыли создать переменную, написали имя с другим регистром букв (Name и name — разные имена), или переменная создана внутри функции, а используется снаружи — про это была статья Функции.
Отдельный случай: NameError: name 'input' is not defined в старых учебниках — они про Python 2. Мы пишем на Python 3, там всё в порядке.
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Дословно: «неподдерживаемые типы операндов для +: целое число и строка». Вы попросили сложить число со строкой, а Python не берётся угадывать, что вы хотели: 2 + "3" — это 5 или "23"?
age = input("Сколько вам лет? ") # input ВСЕГДА возвращает строку
print(age + 1) # TypeError
Лечение — явное преобразование: int(age) + 1. Это классика; про типы всё было в статье Переменные и типы данных.
Родственные сообщения:
TypeError: 'int' object is not subscriptable— «объект типа int не поддерживает индексирование». Вы написалиx[0], гдеx— число. Квадратные скобки работают со списками и строками, а не с числами.TypeError: 'list' object is not callable— «объект типа list не является вызываемым». Вы написалиmy_list(0)с круглыми скобками вместо квадратных.TypeError: average() missing 1 required positional argument: 'numbers'— «пропущен один обязательный аргумент». Вызвали функцию, забыв передать значение.
ValueError: invalid literal for int() with base 10: 'привет'
Дословно: «недопустимая запись для int() по основанию 10: ‘привет’». Тип подходящий (строка — это то, что int() умеет принимать), но содержимое негодное: из слова «привет» число не сделать.
number = int(input("Введите число: ")) # пользователь ввёл "двадцать"
Разница между TypeError и ValueError тонкая, но важная: TypeError — «не тот сорт данных», ValueError — «сорт тот, а значение не годится». Как разница между «вы принесли мне кирпич вместо документа» и «вы принесли документ, но он не заполнен».
IndexError: list index out of range
Дословно: «индекс списка вне диапазона». Попросили элемент, которого нет.
items = ["а", "б", "в"] # индексы: 0, 1, 2
print(items[3]) # IndexError
Помните: нумерация с нуля, последний индекс — len(items) - 1. Эта ошибка настолько распространена, что у неё есть имя: ошибка на единицу (off-by-one error). Про списки — статья Списки, словари и множества.
KeyError: 'город'
Дословно: имя ключа, которого нет в словаре. Никакого пояснения не будет — Python просто показывает, что вы просили.
user = {"имя": "Аня", "возраст": 30}
print(user["город"]) # KeyError: 'город'
Безопасный вариант, если ключа может не быть: user.get("город", "не указан") — вернёт запасное значение вместо падения.
AttributeError: 'str' object has no attribute 'append'
Дословно: «у объекта типа str нет атрибута append». Вы позвали метод, которого у этого типа не существует.
name = "Аня"
name.append("!") # append есть у списков, но не у строк
Часто это признак того, что переменная содержит не то, что вы думаете. Особенно коварно AttributeError: 'NoneType' object has no attribute ... — оно означает, что в переменной лежит None. Обычно потому, что функция ничего не вернула: методы вроде list.sort() и list.append() меняют список на месте и возвращают None, а numbers = numbers.sort() затирает список этим самым None.
ZeroDivisionError, FileNotFoundError, ModuleNotFoundError
ZeroDivisionError: division by zero— деление на ноль. Ищите переменную в знаменателе.FileNotFoundError: [Errno 2] No such file or directory: 'data.txt'— «нет такого файла или каталога». Чаще всего файл существует, но лежит не там, откуда запущена программа. Проверьте текущую папку:import os; print(os.getcwd()).ModuleNotFoundError: No module named 'requests'— «модуль не найден». Библиотека не установлена (pip install requests) или вы назвали свой файл так же, как модуль: файлrandom.pyв своей папке ломаетimport random.
IndentationError внутри работающего кода
Отдельно упомяну коварство: если внутри if или for вы поставите три пробела вместо четырёх — Python может не ругаться сразу, но блоки соберутся не так, как вы задумали. Ошибка станет логической. Настройте автоформатирование в редакторе и не смешивайте руками.
Логические ошибки: когда никто не жалуется
Программа работает. Ошибок нет. Ответ неправильный. Что делать?
def apply_discount(price, percent):
"""Возвращает цену со скидкой."""
return price - percent # ошибка: вычли проценты как рубли
print(apply_discount(1000, 20)) # напечатает 980, а должно быть 800
Никакого traceback. Программа сделала ровно то, что написано. Просто написано не то, что нужно.
Найти такое можно только одним способом: сравнить, что программа делает, с тем, что она должна делать. И для этого нужно уметь заглядывать ей внутрь.
Отладка как расследование
Отладка (debugging) — процесс поиска и устранения ошибок. Слово пошло от буквального жучка (bug, «жук»), застрявшего в реле компьютера Harvard Mark II в 1947 году; страницу журнала с приклеенным мотыльком до сих пор показывают в Смитсоновском музее.
Главная мысль: отладка — это не угадывание, а расследование. Новичок меняет что-нибудь наугад и перезапускает — вдруг заработает. Это может занять часы. Профессионал работает по циклу.
Что именно происходит?
Точный текст, точные данные"] --> B["2. Гипотеза
«Я думаю, дело в том,
что X равно не тому»"] B --> C["3. Проверка
Печатаем X.
Смотрим своими глазами"] C --> D{"Гипотеза
подтвердилась?"} D -- "нет" --> E["Отлично: одним
подозреваемым меньше"] E --> B D -- "да" --> F["4. Исправление
Меняем ОДНУ вещь"] F --> G["5. Перепроверка
Тот же ввод — правильный ответ?
Другие случаи не сломались?"] G -- "не сошлось" --> A
Три правила этого цикла:
- Одна гипотеза за раз. Если поменять три вещи сразу и заработает — вы не знаете, что помогло, и что из этого сломает вам следующий день.
- Опровергнутая гипотеза — это прогресс. Вы не «зря потратили пять минут», вы вычеркнули подозреваемого. Расследование сузилось.
- Не доверяйте своей памяти о коде — смотрите на факты. «Здесь точно лежит число» — самая дорогая фраза в программировании. Напечатайте и убедитесь.
Отладка печатью: print — ваш лучший друг
Самый простой инструмент отладки — вывод на экран. Им пользуются абсолютно все, включая авторов операционных систем. Не стесняйтесь.
def apply_discount(price, percent):
print("ВХОД: price =", price, "| percent =", percent) # что пришло
discount = price * percent / 100
print("посчитали скидку:", discount) # промежуточный шаг
result = price - discount
print("ВЫХОД: result =", result) # что уходит
return result
apply_discount(1000, 20)
Вывод:
ВХОД: price = 1000 | percent = 20
посчитали скидку: 200.0
ВЫХОД: result = 800.0
Три приёма, которые делают отладочный print в разы полезнее:
Подписывайте значения. Голое print(x) в потоке из двадцати чисел бесполезно. Пишите print("x =", x).
Печатайте не только значение, но и тип. Половина загадок вида «почему "5" + 5 падает» решается одной строкой:
value = input("Введите число: ")
print("value =", repr(value), "| тип:", type(value))
# value = '5' | тип: <class 'str'>
repr() показывает значение так, как оно выглядит в коде: строка — в кавычках. Благодаря этому вы видите разницу между числом 5 и строкой '5', а заодно замечаете невидимые пробелы: ' 5 '.
Есть готовый синтаксис. Начиная с Python 3.8 можно писать print(f"{price=}") — напечатается price=1000, имя и значение сразу. Экономит пальцы.
Одно предупреждение: отладочные print — строительные леса. Когда программа заработала, уберите их. Иначе через месяц ваш «готовый» проект будет вываливать в консоль поток мусора, и в нём потонут настоящие сообщения. В больших проектах вместо print используют модуль logging — он умеет включаться и выключаться без правки кода, но это тема для следующего уровня.
Метод деления пополам
Что делать, когда программа на 200 строк выдаёт неверный результат, а где именно ломается — непонятно?
Ищите бинарным поиском. Поставьте print ровно посередине программы и посмотрите, правильны ли данные там. Если да — беда во второй половине. Если нет — в первой. Повторите с оставшейся половиной.
На выходе — неверно"] --> B["print в строке 100:
данные правильные?"] B -- "да" --> C["Виновата вторая половина
(строки 100-200)"] B -- "нет" --> D["Виновата первая половина
(строки 1-100)"] C --> E["print в строке 150"] D --> F["print в строке 50"] E --> G["...и так далее"] F --> G G --> H["За 7-8 проверок находим
виновную строку среди 200"]
Математика здесь на вашей стороне: каждая проверка уменьшает область поиска вдвое. Двести строк — восемь проверок. Тысяча строк — десять. Это тот же принцип, по которому вы ищете слово в бумажном словаре, открывая его посередине, а не листая с первой страницы.
Резиновая уточка
Приём звучит нелепо, а работает лучше половины инструментов. Называется rubber duck debugging.
Возьмите любой неодушевлённый предмет — классика жанра резиновая утка — и объясните ему свой код вслух, строка за строкой. Именно вслух и именно подробно: «так, сюда приходит список оценок… я прохожу по нему циклом… складываю в total… а, стоп».
Это «а, стоп» случается поразительно часто. Причина не в утке: когда вы читаете код глазами, мозг подставляет то, что вы хотели написать. Когда проговариваете вслух — приходится проговорить то, что там написано на самом деле. Расхождение всплывает само.
Если утки нет — сойдёт коллега, кот или сообщение самому себе в мессенджере. Кстати, именно поэтому так часто бывает: начинаешь писать подробный вопрос на форум, доходишь до середины — и понимаешь ответ.
Отладчик: пошаговый просмотр
print хорош, но у него есть предел. Когда нужно смотреть сразу десяток переменных и ходить по программе шаг за шагом, берут отладчик (debugger) — инструмент, который останавливает программу в нужном месте и даёт заглянуть внутрь.
В Python он встроен. Поставьте в код строчку breakpoint() — «точка останова»:
def average(numbers):
total = 0
for n in numbers:
breakpoint() # здесь программа замрёт и отдаст управление вам
total = total + n
return total / len(numbers)
average([10, 20, 30])
Запустите. Программа остановится, и вместо привычного вывода появится приглашение (Pdb). Основные команды:
| Команда | Полное имя | Что делает |
|---|---|---|
p n |
напечатать значение переменной n |
|
pp данные |
pretty print | напечатать красиво (для списков и словарей) |
n |
next | выполнить текущую строку и встать на следующую |
s |
step | шагнуть внутрь функции |
c |
continue | продолжить до следующего breakpoint() |
l |
list | показать код вокруг текущей строки |
w |
where | показать, кто кого вызвал (тот же traceback) |
q |
quit | выйти |
Осторожно с ловушкой: если переменная называется n, то команда n (next) с ней конфликтует — пишите p n, чтобы напечатать.
Если вы работаете в редакторе VS Code (мы ставили его в статье Первая программа), там есть отладчик с кнопками: клик слева от номера строки ставит красную точку останова, F5 запускает, а значения всех переменных видны в панели слева. Это удобнее консольного pdb, и разбираться с ним стоит — но не сегодня. Официальная документация: модуль pdb.
Честный совет: 80% отладки в реальной жизни делается через print. Отладчик достают, когда print не справляется. Не считайте, что «настоящие программисты используют только отладчик» — это миф.
try / except: ошибка, которую вы предвидели
До сих пор мы искали ошибки, чтобы их устранить. Но бывают ошибки, которые устранить невозможно — они зависят не от вас.
Пользователь вводит «двадцать» вместо «20». Файла нет на диске. Интернет отвалился. Вы не можете это предотвратить — но можете предусмотреть, чтобы программа не падала, а вежливо обработала ситуацию.
Для этого есть конструкция try / except:
try:
age = int(input("Введите ваш возраст: "))
except ValueError:
print("Это не похоже на число. Попробуйте ещё раз.")
else:
print("Через год вам будет", age + 1)
finally:
print("Спасибо, что зашли!")
Что означает каждое слово:
try— «попробуй выполнить это». Внутри — код, который может сломаться.except ValueError— «а если случится именноValueError, сделай вот это вместо падения».else— «выполни, только если вtryошибок НЕ было».finally— «выполни в любом случае»: и при успехе, и при ошибке. Обычно тут закрывают файлы и соединения.
которую мы не ловим Падение --> [*]: traceback
Обратите внимание на нижнюю ветку. except ValueError ловит только ValueError. Если внутри try случится TypeError — программа всё равно упадёт. И это правильно.
Отсюда главное правило работы с try:
Никогда не пишите
except:илиexcept Exception: passпросто чтобы «оно не падало».
Такая конструкция глушит все ошибки подряд, включая те, о которых вы не знаете. Программа не упадёт — она будет молча делать неправильные вещи, и вы никогда не узнаете почему. Это как заклеить изолентой лампочку «проверьте двигатель». Лампочка не горит; двигатель горит.
Сравните:
# ПЛОХО: проглотит вообще всё, включая ваши опечатки
try:
result = calculate(data)
except:
result = 0
# ХОРОШО: ловим конкретную предвиденную ситуацию
try:
result = calculate(data)
except ZeroDivisionError:
print("В данных нет ни одной записи — считать нечего")
result = 0
Ловить несколько типов сразу можно так: except (ValueError, TypeError):. А если нужно посмотреть на саму ошибку — except ValueError as e: print("Подробности:", e). Переменная e содержит объект ошибки, и печать его даёт то самое пояснение из traceback.
Свои ошибки: raise
Иногда ошибку нужно поднять самому — когда ваша функция получила заведомо негодные данные и не должна делать вид, что всё нормально:
def apply_discount(price, percent):
if percent < 0 or percent > 100:
raise ValueError(f"Скидка должна быть от 0 до 100, а получили {percent}")
return price - price * percent / 100
apply_discount(1000, 150)
Вывод:
Traceback (most recent call last):
File "main.py", line 6, in <module>
apply_discount(1000, 150)
File "main.py", line 3, in apply_discount
raise ValueError(f"Скидка должна быть от 0 до 100, а получили {percent}")
ValueError: Скидка должна быть от 0 до 100, а получили 150
Это звучит парадоксально — «специально ломать свою программу», — но идея глубокая: упасть рано и громко лучше, чем тихо посчитать неправильно. Скидка 150% превратила бы цену в отрицательную и уехала бы в отчёт. А так вы узнаёте о проблеме сразу, с внятным объяснением и точным адресом.
Как гуглить ошибку
Навык, о котором почему-то не пишут в учебниках, хотя пользуются им все.
Что делать:
- Скопируйте последнюю строку traceback — тип ошибки и пояснение.
- Удалите из неё то, что уникально для вас: имена ваших переменных, пути к файлам, конкретные значения. Останется
TypeError: unsupported operand type(s) for +: 'int' and 'str'. - Добавьте слово
pythonи, если ошибка из библиотеки, её название. - Ищите. Скорее всего, первая же ссылка ведёт на Stack Overflow — сайт вопросов и ответов, где ваша ошибка задана году в 2013-м и разобрана в подробностях.
Как читать ответы: смотрите не только на принятый ответ (с зелёной галочкой), но и на тот, у которого больше голосов, — часто он свежее. И обязательно проверяйте дату: совет 2011 года может относиться к Python 2 и не работать.
Отдельно про нейросети-помощники. Они хорошо объясняют, что означает сообщение об ошибке, — это их сильная сторона. Но обязательно проверяйте предложенное решение запуском, а не доверяйте на слово: уверенный тон не равен правильности. И старайтесь сначала пять минут подумать самостоятельно. Навык самостоятельного поиска причины — это буквально то, за что платят программистам; если сразу отдавать его наружу, он не вырастет.
Как ошибаться реже
Профилактика дешевле лечения. Несколько привычек, которые серьёзно снижают количество боли:
- Пишите маленькими кусками и запускайте часто. Написали 5 строк — запустили. Если сломалось, вы знаете, где: в последних пяти строках. Написали 200 строк и запустили впервые — вас ждёт вечер археологии.
- Давайте переменным осмысленные имена.
total_priceвместоtp. Опечатку вtotal_priceредактор подсветит; опечатку вtp— нет. - Проверяйте граничные случаи. Пустой список, ноль, отрицательное число, очень длинная строка, пользователь нажал Enter не введя ничего. Большинство падений живёт именно на границах.
- Не игнорируйте предупреждения редактора. Жёлтые подчёркивания — это будущие ошибки, которые пока молчат.
- Сохраняйте файл перед запуском. Классика: правите код, запускаете, ничего не меняется, злитесь двадцать минут — а вы запускаете старую сохранённую версию. Ctrl+S.
- Когда застряли на час — встаньте и уйдите. Прогулка, чай, сон. Мозг продолжает работать в фоне, и решение приходит в душе. Это не лень, это известный и надёжный приём.
Практика
Задания от простого к сложному. Первые три — про чтение ошибок, последние — про их поиск.
1. Прочитай и объясни. Запустите каждый фрагмент по очереди, прочитайте traceback и своими словами запишите: тип ошибки, номер строки, причина. Только потом чините.
# а)
numbers = [1, 2, 3]
print(numbers[3])
# б)
name = "Аня"
print("Привет, " + name + "! Тебе " + 30 + " лет")
# в)
user = {"имя": "Пётр"}
print(user["фамилия"])
Подсказка: три разные ошибки — IndexError, TypeError, KeyError. Для (б) пригодится str(30) или f-строка.
2. Найди опечатку по traceback. Не запуская код, скажите, что напечатает Python, — а потом проверьте себя запуском.
temperature = 25
if temperatura > 20:
print("Тепло")
Подсказка: Python не догадывается, что «temperatura» и «temperature» — это одно и то же слово. Как называется ошибка «имя не определено»?
3. Безопасный ввод числа. Напишите программу, которая просит ввести число и печатает его квадрат. Если пользователь ввёл не число — программа должна вежливо сказать об этом и спросить снова, пока не получит корректный ввод.
Подсказка: while True: вокруг блока try / except ValueError, а после успешного преобразования — break. Циклы разбирались в статье Циклы.
4. Логическая ошибка. Функция должна считать средний балл, но выдаёт неверный результат. Найдите ошибку отладкой печатью, а не просто чтением: расставьте print, посмотрите на промежуточные значения.
def average(grades):
total = 0
for g in grades:
total = total + g
return total / len(grades) # что-то здесь не так
print(average([5, 4, 3])) # ожидаем 4.0, а получаем 5.0
Подсказка: напечатайте total и счётчик итераций внутри цикла. Сколько раз цикл вообще выполнился? Посмотрите на отступы очень внимательно — это логическая ошибка, вызванная неверным уровнем вложенности.
5. Надёжный калькулятор среднего (со звёздочкой). Напишите функцию safe_average(numbers), которая:
- если список пустой — поднимает
ValueErrorс понятным сообщением на русском; - если в списке попался элемент, который не число, — сообщает, какой именно элемент и на какой позиции;
- в остальных случаях возвращает среднее.
Затем напишите код, который вызывает её на трёх наборах данных: [10, 20, 30], [] и [1, "два", 3], ловит ошибки через try / except ValueError as e и печатает пояснение, не роняя программу.
Подсказка: проверить «число ли это» можно так: isinstance(x, (int, float)) — функция возвращает True, если x относится к одному из указанных типов. Позицию удобно получать через enumerate(numbers) — он выдаёт пары «индекс, значение».
Мини-итог
Ошибки — не признак вашей некомпетентности, а нормальная часть работы; вопрос только в скорости их разбора.
Их три вида. Синтаксические ловятся до запуска и чинятся быстро — смотрите на указанную строку и на предыдущую. Ошибки времени выполнения дают traceback, и его надо читать снизу вверх: последняя строка — «что», предпоследний кадр — «где», остальное — «как попали». Логические ошибки молчат, и найти их можно только сравнив фактическое поведение с ожидаемым.
Инструменты отладки, в порядке частоты применения: print с подписанными значениями и типами, деление пополам для больших программ, объяснение кода вслух и отладчик breakpoint() — когда всё перечисленное не хватает. Работайте циклом «наблюдение → гипотеза → проверка → одно исправление → перепроверка», а не наугад.
Предвиденные ошибки обрабатывайте через try / except конкретного типа — и никогда не глушите всё подряд пустым except. А когда ваша функция получила заведомую чушь, поднимайте ValueError сами: упасть громко лучше, чем тихо соврать.
Полезное на будущее: встроенные исключения Python — полный список с описаниями, и официальный туториал по ошибкам.
Что дальше
Мы прошли большой путь: переменные, условия, циклы, функции, коллекции, файлы, ошибки. Этого уже достаточно, чтобы писать полезные программы.
Но когда программа вырастает, появляется новая проблема: данные и функции, которые с ними работают, разбредаются по файлу и перестают держаться вместе. У вас есть словарь с данными пользователя и пять отдельных функций, которые его обрабатывают, — и ничто не мешает передать в них не тот словарь.
Решение придумали давно: складывать данные и действия над ними в один свёрток. Знакомимся с объектами.