2013-11-22 2 views
18

Лучший способ объяснить мой вопрос это на примере:Вложенный список постижение сфера

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] 

Когда я запускаю это под питона 2 он отлично работает, но под питона 3 I получить NameError для класса B (но не класс 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 поднять NameError и почему только под Python 3?

+0

Уверены ли вы? http://ideone.com/n87bWm – karthikr

+1

@karthikr Кажется, связано с тем, что это переменная класса: http://ideone.com/7XfDwQ – jpmc26

+0

Протестировано в python 3.3.2 работает нормально. – aIKid

ответ

18

Области классов немного странны в 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).

Стоит отметить, что причиной этой проблемы являются не только внутренние значения последовательности. Как и в previous question, связанный с помощью 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! 

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

+0

Я не совсем понимаю. Если «понимание списка не может видеть никаких переменных класса», то почему я могу увидеть «целые числа» в классе A? Это потому, что я могу получить доступ к целым числам, когда «вызывает» функцию внешнего списка, но не внутри ее тела? – sn6uv

+1

@ sn6uv: поскольку объект, итеративный в первом предложении 'for' для понимания, оценивается во внешней области, а не в области, созданной пониманием. Если вы думаете, что это странно, вы правы. Такое поведение является следствием конструктивного решения, принятого для обнаружения ошибок ранее в выражениях генератора. – user2357112

+0

@ sn6uv: Хм, похоже, я ответил на неправильную часть вопроса. Я думал, что вы спрашиваете то же самое, что и в другом вопросе, который BrenBarn связан как дубликат в комментарии верхнего уровня. Это оказывается чем-то странным. Я буду редактировать, чтобы обновить. – Blckknght

Смежные вопросы