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

Область вложенного списка

Лучший способ объяснить мой вопрос - пример:

example.py:

class A(object):
    integers = [1, 2, 3]
    singles = [i for i in integers]

class B(object):
    integers = [1, 2, 3]
    pairs = [(i, j) for i in integers for j in integers]

Когда я запускаю это под python 2, он работает нормально, но под python 3 я получаю NameError для класса B (но не class A):

$ python example.py
Traceback (most recent call last):
  File "example.py", line 6, in <module>
    class B(object):
  File "example.py", line 8, in B
    pairs = [(i, j) for i in integers for j in integers]
  File "example.py", line 8, in <listcomp>
    pairs = [(i, j) for i in integers for j in integers]
NameError: global name 'integers' is not defined

Почему только класс B вызывает a NameError и почему только в Python 3?

4b9b3361

Ответ 1

Области классов немного странны в Python 3, но это по уважительной причине.

В Python 2 переменные итерации (i и j в ваших примерах) просочились из списков и будут включены во внешнюю область. Это связано с тем, что они были разработаны на ранней стадии проектирования Python 2, и они были основаны на явных циклах. В качестве примера того, как это неожиданно, проверьте значения B.i и B.j в Python 2, где вы не получили ошибку!

В Python 3 были изменены списки, чтобы предотвратить утечку. Теперь они реализованы с помощью функции (которая имеет свою собственную область), которая вызывается для создания значения списка. Это заставляет их работать так же, как генераторные выражения, которые всегда были функциями под обложками.

Следствием этого является то, что в классе понимание списка обычно не может видеть никаких переменных класса. Это параллельно тому, что метод не может напрямую видеть переменные класса (только при self или явном имени класса). Например, вызов метода в классе ниже даст то же самое исключение NameError, которое вы видите в своем понимании списка:

class Foo:
    classvar = "bar"
    def blah(self):
        print(classvar) # raises "NameError: global name 'classvar' is not defined"

Однако существует исключение. Последовательность, повторяющаяся первым предложением for понимания списка, оценивается вне внутренней функции. Вот почему ваш класс A работает в Python 3. Он делает это так, чтобы генераторы могли сразу захватывать неистребимые объекты (а не только когда на них вызывается next и их код работает).

Но он не работает для внутреннего предложения for в двухуровневом понимании в классе B.

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

def f(lst):
    return [i for i in lst]

def g(lst):
    return [(i, j) for i in lst for j in lst]

Здесь разборка f:

>>> dis.dis(f)
  2           0 LOAD_CONST               1 (<code object <listcomp> at 0x0000000003CCA1E0, file "<pyshell#374>", line 2>) 
              3 LOAD_CONST               2 ('f.<locals>.<listcomp>') 
              6 MAKE_FUNCTION            0 
              9 LOAD_FAST                0 (lst) 
             12 GET_ITER             
             13 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
             16 RETURN_VALUE       

Первые три строки показывают f загрузку предварительно скомпилированного кодового блока и создание функции из него (он называет его f.<locals>.<listcomp>). Это функция, используемая для создания списка.

В следующих двух строках отображается загружаемая переменная lst, и из нее делается итератор. Это происходит внутри области f, а не внутренней функции. Затем в качестве аргумента вызывается функция <listcomp> с этим итератором.

Это сопоставимо с классом A. Он получает итератор из переменной класса integers, так же, как вы можете использовать другие типы ссылок на предыдущие члены класса в определении нового члена.

Теперь сравните разборку g, которая делает пары, повторяя один и тот же список дважды:

>>> dis.dis(g)
  2           0 LOAD_CLOSURE             0 (lst) 
              3 BUILD_TUPLE              1 
              6 LOAD_CONST               1 (<code object <listcomp> at 0x0000000003CCA810, file "<pyshell#377>", line 2>) 
              9 LOAD_CONST               2 ('g.<locals>.<listcomp>') 
             12 MAKE_CLOSURE             0 
             15 LOAD_DEREF               0 (lst) 
             18 GET_ITER             
             19 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
             22 RETURN_VALUE         

На этот раз он строит замыкание с объектом кода, а не базовую функцию. Закрытие - это функция с некоторыми "свободными" переменными, которые относятся к вещам в охватывающей области. Для функции <listcomp> в g это работает отлично, поскольку ее область действия является нормальной. Однако, когда вы пытаетесь использовать такое же понимание в классе B, закрытие не выполняется, поскольку классы не позволяют функциям, которые они содержат, видеть в своих областях таким образом (как показано выше в классе Foo).

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

class C:
    num = 5
    products = [i * num for i in range(10)] # raises a NameError about num

Однако вы не получаете ошибку из многоуровневых списков, где внутренние предложения for (или if) относятся только к результатам предшествующих циклов. Это связано с тем, что эти значения не являются частью замыкания, а только локальными переменными внутри области функций <listcomp>.

class D:
    nested = [[1, 2, 3], [4, 5, 6]]
    flattened = [item for inner in nested for item in inner] # works!

Как я уже сказал, области классов немного странные.