Ответ 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!
Как я уже сказал, области классов немного странные.