Как влияет return в finally на выполнение try-finally в Python
Что проверяют
Знаете ли вы точный порядок выполнения try, except и finally, а не только то, что finally «выполняется всегда». Главный подвох в том, что return в finally не просто подменяет значение, а молча глотает исключение. Следом часто спрашивают, что вернёт функция, если в finally поменять переменную, которую возвращает try.
Как отвечать
Порядок такой: выражение в return из блока try вычисляется сразу, результат откладывается, затем выполняется finally. Дальше варианты:
- в
finallyнетreturn: функция возвращает отложенное значение. Если вtryбыло исключение, послеfinallyоно летит дальше; - в
finallyестьreturn: его значение побеждает, отложенный результат теряется; - исключение плюс
returnвfinally: исключение исчезает без следа, функция штатно возвращает значение. Это касается иKeyboardInterrupt, иSystemExitотsys.exit(); breakиcontinueвfinally: внутри цикла ведут себя так же и тоже гасят исключение.
Пример, который стоит показать:
def f():
x = 1
try:
return x
finally:
x = 2Вернётся 1: значение уже вычислено, присваивание в finally на него не влияет. Если бы возвращался список, а finally добавил в него элемент, изменение было бы видно, потому что объект тот же.
Стоит упомянуть, что язык сам предупреждает об этой ловушке: PEP 765 в Python 3.14 добавил SyntaxWarning на return, break и continue, которые выходят из блока finally. Линтеры ловят это давно: правило B012 в Ruff, lost-exception в Pylint.
Где спотыкаются
Говорят, что return в finally меняет только возвращаемое значение. Потеря исключения важнее: ошибка из try не попадёт ни в лог, ни выше по стеку.
Думают, что изменение переменной в finally меняет результат. Для неизменяемых объектов нет, для изменяемых меняется сам объект, а не ссылка.
Утверждают, что finally выполнится при любых условиях. Он не выполнится при os._exit(), убийстве процесса сигналом SIGKILL или падении интерпретатора, поэтому освобождение внешних ресурсов не стоит строить только на нём.