Что вернёт эта функция в Python?

от автора

Всем привет! Сегодня хотел бы обсудить очень простой, но, на мой взгляд, интересный вопрос по 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

Опуская излишние подробности, этот код ведёт себя так:

  1. Устанавливаем блок try и указываем, где находится finally.

  2. Загружаем константу и возвращаем значение.

  3. Выполняем некоторые вспомогательные действия.

  4. Наконец идёт блок finally (адреса 11, 14, 15), в которым мы снова загружаем константу и делаем ret.

При исполнении кода сначала отрабатывает часть в блоке try, а затем выполняется код из finally. Что же происходит, когда мы снова вызовем RETURN_VALUE? Правильно, мы просто перезапишем возвращаемое значение retval на новое. Ну а функция, разумеется, вернёт 2.

Как видите, даже несмотря кажущуюся неочевидность, Python, на мой взгляд, ведёт себя максимально понятно и логично: блок finally выполняется после блока try и его возвращаемое значение «более актуально». Однако, разумеется, на практике писать такой код я крайне не рекомендую 😉


ссылка на оригинал статьи https://habr.com/ru/articles/563764/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *