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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

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


Интересные статьи

Интересные статьи

Нередко при работе с Bitrix24 REST API возникает необходимость быстро получить содержимое определенных полей всех элементов какого-то списка (например, лидов). Традиционн...
Сейчас практически каждый разработчик знаком с понятием «асинхронность» в программировании. В эру, когда информационные продукты настолько востребованы, что вынуждены обрабатывать одн...
Привет, друзья! Меня зовут Петр, я представитель малого белорусского бизнеса со штатом чуть более 20 сотрудников. В данной статье хочу поделиться негативным опытом покупки 1С-Битрикс. ...
В 1С-Битрикс: Управление сайтом (как и в Битрикс24) десятки, если не сотни настраиваемых типов данных (или сущностей): инфоблоки, пользователи, заказы, склады, форумы, блоги и т.д. Стр...
Некоторое время назад мне довелось пройти больше десятка собеседований на позицию php-программиста (битрикс). К удивлению, требования в различных организациях отличаются совсем незначительно и...