Всем привет! Сегодня хотел бы обсудить очень простой, но, на мой взгляд, интересный вопрос по Python и его внутреннему устройству. Как вы думаете, что вернёт эта функция:
def foo():
try:
return 1
finally:
return 2
Если вам интересно, что получится в результате и как это работает, добро пожаловать под кат.
Прежде чем давать ответ, давайте разберёмся, что происходит. Для начала рассмотрим самую простую функцию:
def foo():
return 1
Распечатаем её байт код:
import dis
dis.dis(foo)
Мы увидим следующий вывод:
2 0 LOAD_CONST 1 (1)
3 RETURN_VALUE
Рассмотрим по шагам:
LOAD_CONST
загружает константу (в нашем случае1
) и кладет её на вершину стека.RETURN_VALUE
возвращает в вызывающий код значение с вершины стека.
Подробнее о байт-коде Python и его командах рассказано тут.
Что же скрывается за мифической фразой «возвращает в вызывающий код»? На самом деле, никакой магии не происходит. Если обратиться к исходному коду CPython, то можно увидеть следующие строчки:
switch (opcode) {
...
case RETURN_VALUE: {
retval = POP();
why = WHY_RETURN;
goto fast_block_end;
}
...
}
Как видите, всё очень просто и понятно: мы сохраняем в переменной retval
значение с вершины стека и переходим к выходу из текущего блока.
Теперь мы готовы посмотреть на байт-код функции из нашего исходного примера. Как же она устроена внутри?
2 0 SETUP_FINALLY 8 (to 11)
3 3 LOAD_CONST 1 (1)
6 RETURN_VALUE
7 POP_BLOCK
8 LOAD_CONST 0 (None)
5 >> 11 LOAD_CONST 2 (2)
14 RETURN_VALUE
15 END_FINALLY
Опуская излишние подробности, этот код ведёт себя так:
Устанавливаем блок
try
и указываем, где находитсяfinally
.Загружаем константу и возвращаем значение.
Выполняем некоторые вспомогательные действия.
Наконец идёт блок
finally
(адреса 11, 14, 15), в которым мы снова загружаем константу и делаемret
.
При исполнении кода сначала отрабатывает часть в блоке try
, а затем выполняется код из finally
. Что же происходит, когда мы снова вызовем RETURN_VALUE
? Правильно, мы просто перезапишем возвращаемое значение retval
на новое. Ну а функция, разумеется, вернёт 2
.
Как видите, даже несмотря кажущуюся неочевидность, Python, на мой взгляд, ведёт себя максимально понятно и логично: блок finally
выполняется после блока try
и его возвращаемое значение «более актуально». Однако, разумеется, на практике писать такой код я крайне не рекомендую ;-)
prostofilya
Я теперь понял почему при подаче заявки на ипотеку через домклик возвращается null
P.S. пояснение: сбер при неудовлетворении заявки просто сообщает что клиенту отказано, однако многие другие банки подробно поясняют причину, предлагают хотя бы меньшую сумму. Не знаю, может это не массово, но слышал от нескольких человек и лично с этим тоже сталкивался. / сори за оффтоп
NetBUG
int(null) = 0, всё логично.