Подтвердить что ты не робот

Переменные Python теряют объем внутри генератора?

Код ниже возвращает NameError: global name 'self' is not defined. Почему?

lengths = [3, 10]
self.fooDict = getOrderedDict(stuff)

if not all(0 < l < len(self.fooDict) for l in lengths):
    raise ValueError("Bad lengths!")

Обратите внимание, что self.fooDict - это OrderedDict (импортированный из библиотеки коллекций), содержащий 35 записей. Когда я пытаюсь отлаживать, приведенный ниже код выполняется без ошибок:

(Pdb) len(self.dataDict)
35
(Pdb) all(0 < size < 35 for size in lengths)
True

Но ниже код debugginf дает мне исходную ошибку:

(Pdb) baz = len(self.dataDict)
(Pdb) all(0 < size < baz for size in lengths)
NameError: global name 'baz' is not defined
4b9b3361

Ответ 1

Краткий ответ и обходное решение

У вас есть ограничение на отладчик. Выражения, введенные в отладчик, не могут использовать значения, не связанные с локальным охватом, поскольку отладчик не может создать необходимые закрытия.

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

def _test(baz, lengths):
    return all(0 < size < baz for size in lengths)

_test(len(self.dataDict), lengths)

Обратите внимание, что это относится также к понятию набора и словаря, а в Python 3 - к пониманию списка.

Долгий ответ, почему это происходит

Выражения генератора (и определения списка, dict и Python 3) запускаются в новом, вложенном пространстве имен. Имя baz в выражении генератора не является локальным в этом пространстве имен, поэтому Python должен найти его где-то в другом месте. Во время компиляции Python определяет, откуда это имя. Он будет искать из областей, доступных компилятору, и если совпадений нет, объявляет имя глобальным.

Вот два генераторных выражения для иллюстрации:

def function(some_iterable):
    gen1 = (var == spam for var in some_iterable)

    ham = 'bar'
    gen2 = (var == ham for var in some_iterable)

    return gen1, gen2

Имя spam не найдено в родительской области, поэтому компилятор отмечает это как глобальное:

>>> dis.dis(function.__code__.co_consts[1])  # gen1
  2           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                17 (to 23)
              6 STORE_FAST               1 (var)
              9 LOAD_FAST                1 (var)
             12 LOAD_GLOBAL              0 (spam)
             15 COMPARE_OP               2 (==)
             18 YIELD_VALUE         
             19 POP_TOP             
             20 JUMP_ABSOLUTE            3
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE        

Код операции в индексе 12 использует LOAD_GLOBAL для загрузки имени spam.

Имя ham находится в области видимости функции, поэтому компилятор генерирует байт-код для поиска имени как закрытия из функции. В то же время имя ham отмечено как закрытие; переменная обрабатывается по-разному кодом, сгенерированным для function, поэтому вы можете ссылаться на него, когда функция вернулась.

>>> dis.dis(function.__code__.co_consts[3])  # gen2
  4           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                17 (to 23)
              6 STORE_FAST               1 (var)
              9 LOAD_FAST                1 (var)
             12 LOAD_DEREF               0 (ham)
             15 COMPARE_OP               2 (==)
             18 YIELD_VALUE         
             19 POP_TOP             
             20 JUMP_ABSOLUTE            3
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE        
>>> function.__code__.co_cellvars  # closure cells
('ham',)

Имя ham загружается кодом операции LOAD_DEREF, а объект-код функции указывает это имя как закрытие. Когда вы разбираете function, вы можете найти среди других байт-код:

>>> dis.dis(function)
  # ....

  4          22 LOAD_CLOSURE             0 (ham)
             25 BUILD_TUPLE              1
             28 LOAD_CONST               3 (<code object <genexpr> at 0x1074a87b0, file "<stdin>", line 4>)
             31 MAKE_CLOSURE             0
             34 LOAD_FAST                0 (some_iterable)
             37 GET_ITER            
             38 CALL_FUNCTION            1
             41 STORE_FAST               2 (gen2)

  # ...

где байт-коды LOAD_CLOSURE и MAKE_CLOSURE создают закрытие для ham, которое будет использоваться объектом кода генератора.

При выполнении произвольных выражений в отладчике компилятор не имеет доступа к пространству имен, которое вы отлаживаете. Что еще более важно, он не может изменить это пространство имен, чтобы создать закрытие. Таким образом, вы не можете использовать ничего, кроме глобалов, в выражениях генератора.