Python3: список представлений и стековые фреймы - PullRequest
0 голосов
/ 19 февраля 2019

Рассмотрим эту функцию:

def quux():
    i = 42 
    print([i for x in [1]])

Результат: [42]

Поэтому я предполагаю, что локальные переменные видны в списках.

Теперь рассмотрим это:

def foo():
    return currentframe().f_back.f_locals["i"]

def quux():
    i = 42 
    print([foo() for x in [1]])

Результат:

KeyError: 'i'

При проверке кадров стека выясняется, что в него вставлен дополнительный стек.между кадрами для quux и foo:

{'x': 1, '.0': <tuple_iterator object at 0x7f59eb94c860>}

Хорошо, достаточно справедливо.Что меня озадачивает, так это то, почему первый пример видит i.Если есть дополнительный фрейм стека, его не должно быть видно, не так ли?

И на более практических основаниях, как я могу получить фрейм стека вызывающей функции, независимо от того, была ли моя функция вызвана извне или из спискапонимание или, если на то пошло, из нескольких вложенных списков?

Ответы [ 2 ]

0 голосов
/ 19 февраля 2019

То, что дает видимость переменной, это ее область действия , а не возможные кадры.Локальные переменные Python имеют область действия функции, поэтому в первом примере переменная i видна из любой строки внутри функции quux.

Кадры - это просто детали реализации CPython.Из документации стандартной библиотеки для модуля inspect:

inspect.currentframe ()Возвращает объект frame для стекового фрейма вызывающего.

Подробности реализации CPython: эта функция опирается на поддержку стекового фрейма Python в интерпретаторе, которая не гарантируется во всех реализациях Python.Если выполняется в реализации без поддержки фреймов стека Python, эта функция возвращает None.

Когда реализация использует поддержку фреймов стека Python, реализация будет иметь фреймы для явных вызовов функций и может добавлять чередующиеся фреймы для внутреннихиспользование здесь для понимания списка, но могут быть и другие варианты использования.

Таким образом, здесь у вас есть 2 возможных варианта:

  • прагматично: вы обнаружили, что в этомВ случае использования существует только один чередующийся кадр, поэтому вы можете просто пропустить его:

    def foo():
        return currentframe().f_back.f_back.f_locals["i"]
    
  • Документация мудрая:

    Вы знаете, что внешний кадр будет содержать i локальная переменная.Просто отсканируйте внешние кадры для первого, который содержит его:

    def foo():
        for f in getouterframes(currentframe()):
            if 'i' in f.frame.f_locals:
                return f.frame.f_locals['i']
        return None
    
0 голосов
/ 19 февраля 2019

Это очень хорошее наблюдение (независимо от его полезности :)).Лучший способ ответить на ваш вопрос - убедиться в этом: сбросить разборку (используя dis.dis(quux) в двух версиях).Вы заметите важное различие в двух версиях - это закрытие, загружаемое в первой версии.Это потому, что вы ссылались на переменную i в объекте понимания списка и тем самым сделали ia частью замыкания, и список comp имеет к ней доступ.Во втором случае таких вещей нет, поэтому вы получаете эту ошибку.

Что касается второй части, теперь, когда вы ее поняли, возможно, вы захотите перефразировать ее?

...