Какой результат вернёт функция, если блок finally выполнит return после return в try?

Какой результат вернёт функция, если блок finally выполнит return после return в try?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Функция вернёт значение из finally: его return заменяет ранее подготовленный результат из try. Это происходит потому, что передача управления после return из try откладывается до завершения finally.

Если finally содержит return, он также может подавить исключение, возникшее в try, поэтому такой приём обычно считается опасным.

Исторический контекст

Конструкция try/finally предназначена для гарантированного выполнения завершающих действий: освобождения ресурса, снятия блокировки или восстановления состояния. Блок finally запускается независимо от того, завершился try обычным образом, оператором return или исключением.

Для этого Python не завершает функцию немедленно при встрече return внутри try. Сначала вычисляется возвращаемое значение, затем выполняется finally, и только после этого окончательно выбирается способ завершения функции.

Постановка проблемы

Разработчик может ожидать, что return в try всегда определяет результат функции. Однако finally способен изменить этот результат, выбросить другое исключение или подавить исходное исключение.

Из-за этого добавление return в finally может незаметно изменить контракт функции: вызывающий код получит другое значение или не увидит ошибку, которую должен был обработать.

Подробное решение

Механизм работает так: Python вычисляет выражение в return внутри try и запоминает результат. Затем выполняется finally. Если finally завершается без нового return, исключения или другого изменения управления, функция возвращает ранее вычисленное значение.

Если же finally содержит return, именно он становится окончательным результатом:

def choose(): try: return "try" finally: return "finally" print(choose()) # finally

return в finally имеет приоритет не только над результатом try, но и над исключением из try. Например, если в try возникает ValueError, а finally возвращает значение, исключение не дойдёт до вызывающего кода.

Практическое правило: не использовать return в finally без очень веской причины. В finally следует размещать очистку ресурсов, а результат возвращать после неё либо использовать контекстный менеджер, который явно описывает управление ресурсом.

Ситуация из практики

Функция читает данные из временного файла и должна закрыть файл перед завершением. Один вариант — поместить закрытие и return в finally. Его плюс — файл действительно закрывается, но минус — ошибка чтения может быть подавлена возвращаемым значением из finally.

Другой вариант — вернуть результат из try, а закрытие выполнить в finally без return. Он сохраняет исходное исключение и корректный результат, но требует внимательно следить, чтобы очистка сама не создала новую ошибку.

Предпочтительный вариант — использовать контекстный менеджер with: он отделяет получение результата от управления ресурсом и снижает риск случайно подавить исключение. Такой код проще проверять и безопаснее расширять, поэтому именно этот подход обычно выбирают в прикладном Python-коде.

Что кандидаты часто упускают

  1. Что произойдёт, если return в try возвращает изменяемый объект, а finally изменяет его?

    Выражение возврата вычисляется до выполнения finally, но объект не копируется автоматически. Если finally изменит этот объект, вызывающий код получит уже изменённое состояние того же объекта. Переназначение локального имени в finally само по себе объект, который уже подготовлен к возврату, не заменяет; важна именно мутация объекта.

  2. Подавляет ли finally исключение, если в нём нет return?

    Нет. Если finally нормально завершился, исходное исключение продолжает распространяться к вызывающему коду. Оно будет заменено только новым исключением или иной конструкцией управления, например return, выполненной внутри finally.

  3. Что произойдёт, если исключение возникнет непосредственно внутри finally?

    Новое исключение станет основной причиной завершения функции и обычно заменит результат или исключение из try. Поэтому операции очистки в finally тоже должны быть надёжными; при необходимости ошибки очистки следует обрабатывать отдельно, не скрывая первичную ошибку.