Исключения и отладка: вопросы для собеседования (часть 4)
try/except, raise, типы исключений, отладка с pdb — на собеседовании спрашивают, как обрабатывать ошибки и что происходит при необработанном исключении. Просят объяснить разницу между ValueError и TypeError, показать try/except/finally. Грамотная обработка ошибок отличает продакшн-код от скрипта в Jupyter.
Вопросы 16–20 из 20
16Вы пишете функцию для расчёта метрики и понимаете, что без обязательного поля `user_id` результат будет неверным. Что лучше сделать, чтобы не «тихо» считать мусор и облегчить отладку?
AВернуть 0 и продолжить расчёт, чтобы дашборд не «краснел» и пайплайн дочитал остальные строки
BВернуть `None` без объяснений причины, чтобы обработка следующих записей не упала на ошибке
CНапечатать сообщение через `print` и продолжить выполнение, посчитав метрику дальше как обычно
DПоднять явную ошибку `raise ValueError('user_id is required')`, чтобы вызывающий код её увидел
Ответ: Если без данных метрика бессмысленна, лучше явно остановиться через `raise`, чем скрывать проблему.
Возврат заглушек вроде 0 или `None` может незаметно испортить отчёт и сделать проблему трудноуловимой. Если поле обязательно, это нарушение контракта входных данных, и правильнее явно сигнализировать через `raise`. Так ошибка быстро обнаруживается, а сообщение в трассировке помогает найти источник некорректных данных. Напечатать через `print` тоже плохо: лог легко потеряется, а функция продолжит считать неверный результат.
Подробный разбор → 17Вы поймали ошибку, залогировали её и хотите пробросить дальше, чтобы пайплайн упал и трейс сохранился. Что нужно сделать внутри `except`?
AСделать `return None`, чтобы функция тихо завершилась без проброса исключения наружу
BСделать `pass`, чтобы блок выполнился без действий и пайплайн продолжил работу дальше
CСделать `raise Exception('error')`, чтобы поднять новое исключение вместо текущего
DСделать `raise` без аргументов, чтобы пробросить текущее исключение дальше с traceback
Ответ: Внутри `except` команда `raise` без аргументов перекидывает текущее исключение дальше.
Если ошибка не является ожидаемой и не должна быть «исправлена» на месте, лучше её не скрывать. `raise` без аргументов сохраняет оригинальный тип исключения и его трейс, что сильно облегчает отладку. Если вместо этого поднять новое исключение без контекста, можно потерять важные детали о первопричине.
Подробный разбор → 18Поле `age_text` приходит как строка и может быть `"18"`, пустой строкой или `"N/A"`. Вы хотите получить число или `None`, не скрывая неожиданные баги. Какой подход наиболее практичный в прикладной аналитике без регулярных выражений?
AВызывать `int(age_text)` без проверок типа `try`/`except`: редкие ошибки рушат процесс и попадают в логи
BПрименять `float(age_text)` с последующим `round(...)`: формат входа `"18"` или `"N/A"` на результат не влияет
CСначала `s = age_text.strip()`, далее при `s.isdigit()` брать `int(s)`, иначе вернуть `None`: прочие случаи логировать
DИспользовать `eval(age_text)` и считать данные доверенными: выражение превратит строку `"18"` в число автоматически
Ответ: Иногда лучше предварительная проверка, например `strip()` и `isdigit()`, чем массовый `try`/`except` на любые ошибки.
Сравните два подхода: обработка через исключения и предварительная проверка условий. Если формат простой — цифры или пусто — проверка через `strip()` и `isdigit()` делает поведение явным и уменьшает количество исключений в потоке. Неожиданные случаи можно логировать отдельно, а не скрывать слишком широким `except`. Безусловный `int()` ломается на пустой строке, `float()` с округлением не отличает невалидные значения, а `eval()` опасен на любых внешних данных.
Подробный разбор → 19В блоке `try` произошла ошибка, и в `finally` тоже случилось исключение (например, при закрытии ресурса). Почему это плохо с точки зрения отладки?
AЭто плохо только из-за производительности, но на отладку и поиск первопричины это никак не влияет
BНичего страшного в этом нет: исключение из блока `finally` Python автоматически игнорирует и логирует
CИсключение из `finally` может «перекрыть» исходную ошибку и затруднить поиск первопричины по traceback
DЭто даже хорошо: вы увидите оба traceback одновременно и быстро поймёте, что именно упало в коде
Ответ: Если `finally` падает, он может скрыть исходную причину падения и усложнить диагностику.
`finally` нужен для безопасной уборки ресурсов. Если в нём есть код, который сам может упасть, появляется риск потерять фокус на исходном исключении из `try`. Python в этом случае поднимет именно ошибку из `finally`, а исходное исключение становится «причиной» (`__context__`) и легко теряется в логах. В результате вместо первопричины в трейсбеке доминирует поздняя ошибка, и исправление дольше и дороже.
Подробный разбор → 20Вы пишете функцию `def get_user(user_id): ...` и ожидаете, что `user_id` — число. Если кто-то передал строку `'42'`, какое исключение обычно уместнее поднять, чтобы явно сообщить о неверном типе входных данных?
AПоднять `ValueError('bad type')`: применяется при допустимом типе, но недопустимом значении
BПоднять `TypeError('user_id must be int')`: стандартный сигнал о несовпадении типа аргумента
CПоднять `KeyError('user_id')`: исключение для отсутствующего ключа в словаре или mapping
DПоднять `IndexError('user_id')`: исключение при выходе за границы последовательности
Ответ: Неверный тип аргумента — частый случай для `TypeError`; неверное значение при корректном типе — для `ValueError`.
Разделяйте причины ошибок: если аргумент пришёл не того типа (строка вместо числа), это проблема типа и уместен `TypeError`. Если тип правильный, но значение недопустимо (например, отрицательное число там, где нельзя), используют `ValueError`. `KeyError` поднимают при отсутствующем ключе в словаре, `IndexError` — при выходе за границы последовательности; для проверки типа аргумента ни то, ни другое не подходит.
Подробный разбор →