Каковы правила определения контекста списка в классе Python?

В следующем коде ассистент mc отлично работает в Python 2 и 3.

Назначение cc, которое использует одно и то же понимание в классе, работает в Python 2, но с Python 3 не работает.

Что объясняет это поведение?

ml1 = "a b c".split()
ml2 = "1 2 3".split()
mc = [ i1 + i2 for i1 in ml1 for i2 in ml2 ]

class Foo(object):
    cl1 = ml1
    cl2 = ml2

    cc1 = [ i1 for i1 in cl1 ]
    cc2 = [ i2 for i2 in cl2 ]
    cc = [ i1 + i2 for i1 in cl1 for i2 in cl2 ]


print("mc = ", mc)
foo = Foo()
print("cc = ", foo.cc)

Я получаю это:

(default-3.5) snafu$ python2 /tmp/z.py 
('mc = ', ['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3'])
('cc = ', ['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3'])

(default-3.5) snafu$ python3 /tmp/z.py 
Traceback (most recent call last):
  File "/tmp/z.py", line 5, in <module>
    class Foo(object):
  File "/tmp/z.py", line 11, in Foo
    cc = [ i1 + i2 for i1 in cl1 for i2 in cl2 ]
  File "/tmp/z.py", line 11, in <listcomp>
    cc = [ i1 + i2 for i1 in cl1 for i2 in cl2 ]
NameError: name 'cl2' is not defined

Почему переменная класса cl2 не определена? Обратите внимание, что назначение cc2 работает отлично, как и cc1. Перестановка cl1 и cl2 в понимании показывает, что второй цикл является тем, который вызывает исключение, а не cl2 как таковой.)

Версии:

(default-3.5) snafu$ python2 --version
Python 2.7.11+
(default-3.5) snafu$ python3 --version
Python 3.5.1+

Ответ 1

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

class Example:
    var = 1
    def this_fails(self):
        print(var)
Example().this_fails()  # NameError

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

class Foo(object):
    ...
    def make_cc(outer_iterable):
        result = []
        for i1 in outer_iterable:
            for i2 in cl2:  # This fails
                result.append(i1 + i2)
        return result
    cc = make_cc(cl1)  # cl1 is evaluated outside the comprehension scope, for reasons

Обратите внимание, что поиск cl1 работает отлично, потому что это происходит в классе, вне понимания, несмотря на синтаксическое вложение внутри понимания. Они приняли это решение, когда Python представил genexps, потому что раньше он обнаружил несколько общих ошибок genexp. Именно поэтому работают функции cc1 и cc2; их единственное использование переменных уровня класса находится в их внешнем (только) for итерабельном.

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