- PVSM.RU - https://www.pvsm.ru -
Всем привет! Сегодня хотел бы обсудить очень простой, но, на мой взгляд, интересный вопрос по 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 и его командах рассказано тут [1].
Что же скрывается за мифической фразой «возвращает в вызывающий код»? На самом деле, никакой магии не происходит. Если обратиться к исходному коду 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
и его возвращаемое значение «более актуально». Однако, разумеется, на практике писать такой код я крайне не рекомендую ;-)
Автор: Кузьмин Алексей
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/365450
Ссылки в тексте:
[1] тут: https://docs.python.org/3/library/dis.html#dis.Instruction
[2] Источник: https://habr.com/ru/post/563764/?utm_source=habrahabr&utm_medium=rss&utm_campaign=563764
Нажмите здесь для печати.