Исключения и отладка: вопросы для собеседования (часть 3)
try/except, raise, типы исключений, отладка с pdb — на собеседовании спрашивают, как обрабатывать ошибки и что происходит при необработанном исключении. Просят объяснить разницу между ValueError и TypeError, показать try/except/finally. Грамотная обработка ошибок отличает продакшн-код от скрипта в Jupyter.
Вопросы 11–15 из 20
11Вы пишете функцию, которая внутри `try` делает `return`, а в `finally` закрывает ресурс (например, файл или соединение). Что произойдёт с кодом в `finally` при `return` из `try`?
AБлок `finally` выполнится перед фактическим выходом из функции, после чего вернётся значение.
BБлок `finally` будет пропущен, потому что `return` уже передал управление наружу из функции.
CБлок `finally` сработает только в том случае, если внутри `try` было поднято исключение.
DБлок `finally` сработает только в том случае, если из `try` возвращается значение `None`.
Ответ: `finally` выполняется даже при `return`, поэтому подходит для освобождения ресурсов.
Главная роль `finally` — гарантировать освобождение ресурсов независимо от пути выполнения. Даже если внутри `try` сработал `return`, интерпретатор сначала выполнит `finally`, и только потом завершит функцию и вернёт значение. То же самое произойдёт при выходе через исключение или `break`. Это поведение важно для предотвращения утечек файловых дескрипторов и сетевых соединений.
Подробный разбор → 12После запроса в базу вы получаете список `rows`, который иногда бывает пустым. Код `rows[0]` падает с `IndexError`. Какой вариант лучше, если пустой результат — ожидаемый случай?
AСделать проверку `if rows:` перед `rows[0]`, и для пустого случая вернуть `None` или обработать ветку отдельно
BОбернуть весь блок в `try` и ловить `except Exception:`, не различая виды ошибок и пропуская пустой случай молча
CПисать `rows[0]` без проверки и игнорировать `IndexError` через пустой `except`, считая его несущественным
DСделать `raise IndexError('no rows')`, чтобы код всегда падал на пустом результате и требовал ручного разбора
Ответ: Если пустой список возможен по данным, лучше явная проверка, чем ловля ошибок «на всякий случай».
Если пустой список — нормальная ситуация по данным, явная проверка `if rows:` делает логику видимой и помогает читателю кода. Исключения нужны для неожиданных ошибок, а не для штатных веток. Ловля `Exception` слишком широка и легко скроет реальные баги — например, опечатку в имени переменной. Принудительный `raise` на пустом результате тоже не решает задачу, а превращает обычный случай в аварийный.
Подробный разбор → 13Вы преобразуете поле из CSV: `price = int(text)`. Иногда там встречается `'N/A'`. Какой вариант обработки лучше всего соответствует цели «ловить только ожидаемую ошибку, а не всё подряд»?
A`except Exception:` с `price = 0`: ловит широкий класс ошибок, включая баги в логике скрипта
B`except ValueError:` с обработкой плохого значения: ожидаемое исключение от `int(...)` ловится точечно
CГолый `except:` с `pass`: обрабатывает любые исключения, в том числе `KeyboardInterrupt` и `SystemExit`
DБез `try/except`: скрипт падает на первом нечисловом значении в поле `text`
Ответ: Ловите ожидаемые исключения конкретных типов, например `ValueError` при `int(...)`.
В аналитических пайплайнах часть ошибок ожидаема — например, плохие значения в полях. Но `except Exception:` опасен: он скроет и баги в коде, которые лучше увидеть. Голый `except:` ещё хуже: он проглотит даже `KeyboardInterrupt`. Правильнее ловить конкретное исключение `ValueError` для некорректного значения — обработка контролируемая, а отладка — простая.
Подробный разбор → 14Вы обрабатываете события, где поле `utm_source` может отсутствовать, и это нормальная ситуация по данным. Какой подход обычно лучше, чем перехватывать `KeyError` ради подстановки значения по умолчанию?
AПревратить событие в строку через `str(event)` и искать подстроку, при отсутствии подставлять значение по умолчанию
BБрать значение через `event['utm_source']` и оборачивать конвейер в `except Exception:` для перехвата ошибок ключей
CИспользовать `event.get('utm_source', 'unknown')` для получения значения по умолчанию без возбуждения исключения
DПринудительно возбуждать `raise KeyError('utm_source')`, чтобы заполнить значение по умолчанию в обработчике
Ответ: Если отсутствие ключа ожидаемо, удобнее использовать `dict.get(...)`, чем перехватывать `KeyError`.
Исключения подходят для действительно исключительных ситуаций. Если поле может отсутствовать по данным, вызов `event.get('utm_source', 'unknown')` делает намерение явным, упрощает код и уменьшает шум при отладке. Перехват `KeyError` уместен, когда ключ обязан быть и его отсутствие — нарушение контракта; ловить исключения «на всякий случай» через широкий `except Exception:` опасно, потому что заглушает и настоящие ошибки.
Подробный разбор → 15В коде есть два обработчика: `except ValueError:` и `except Exception:`. Почему порядок этих блоков `except` важен?
AПорядок обработчиков `except` не важен: интерпретатор Python всё равно выберет самый подходящий блок по типу исключения.
BБолее специфичные исключения (например, `ValueError`) должны идти раньше, иначе их перехватит более общий блок `except Exception:`.
CБлок `except Exception:` всегда должен идти первым, чтобы «ускорить» обработку и сразу ловить любые ошибки в одном месте.
DПорядок блоков `except` зависит только от версии Python и не имеет реального значения для логики обработки ошибок в коде.
Ответ: Сначала пишут специфичные `except`, потом более общие, иначе логика обработки ломается.
Python проверяет блоки `except` сверху вниз. Если поставить `except Exception:` раньше, он перехватит почти всё и до `except ValueError:` дело не дойдёт. Это ухудшает диагностику и приводит к неправильной обработке ошибок: смысл точечных блоков `except` теряется, потому что они становятся недостижимы.
Подробный разбор →