Область видимости и список классов, набор или словарь, а также выражения генератора не смешиваются.
Почему; или официальное слово об этом
В Python 3 для списочных представлений была назначена собственная область видимости (локальное пространство имен), чтобы их локальные переменные не могли перетекать в окружающую область видимости (см. Перечень пониманий списков Python связывает имена даже после объема понимания. Это правильно? ). Это замечательно, если использовать такое понимание списка в модуле или в функции, но в классах определение области видимости немного странно .
Это задокументировано в ОП 227 :
Имена в области видимости не доступны. Имена разрешаются в самой внутренней области действия функции. Если определение класса встречается в цепочке вложенных областей, процесс разрешения пропускает определения класса.
и в class
документации составного заявления :
Затем набор класса выполняется в новом фрейме выполнения (see section Именование и связывание ), используя недавно созданное локальное пространство имен и исходное глобальное пространство имен. (Обычно набор содержит только определения функций.) Когда набор класса завершает выполнение, его кадр выполнения отбрасывается, но его локальное пространство имен сохраняется . [4] Объект класса затем создается с использованием списка наследования для базовых классов и сохраненного локального пространства имен для словаря атрибутов.
Акцент мой; фрейм выполнения - это временная область.
Поскольку область видимости повторно используется в качестве атрибутов объекта класса, что позволяет использовать ее как нелокальную область действия, что ведет к неопределенному поведению; что произойдет, если метод класса, называемый x
вложенной областью видимости, затем также манипулирует Foo.x
, например? Что еще более важно, что бы это значило для подклассов Foo
? Python должен относиться к области видимости класса по-другому, поскольку она сильно отличается от области видимости функции.
Наконец, но не в последнюю очередь, в связанном разделе « Именование и привязка » в документации по модели выполнения явно упоминаются области действия классов:
Область имен, определенных в блоке класса, ограничена блоком класса; он не распространяется на блоки кода методов - это включает в себя понимания и выражения генератора, поскольку они реализованы с использованием области действия функции. Это означает, что следующее не удастся:
class A:
a = 42
b = list(a + i for i in range(10))
Итак, подведем итог: вы не можете получить доступ к области видимости класса из функций, списков или выражений генератора, заключенных в эту область; они действуют так, как будто эта область не существует. В Python 2, списочные понимания были реализованы с помощью ярлыка, но в Python 3 они получили свою собственную область действия функций (как и следовало иметь), и поэтому ваш пример ломается. Другие типы понимания имеют свою собственную область видимости независимо от версии Python, поэтому аналогичный пример с пониманием set или dict сломался бы в Python 2.
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
(Небольшое) исключение; или почему одна часть все еще может работать
Есть одна часть выражения понимания или генератора, которая выполняется в окружающей области, независимо от версии Python. Это было бы выражением для самой внешней итерации. В вашем примере это range(1)
:
y = [x for i in range(1)]
# ^^^^^^^^
Таким образом, использование x
в этом выражении не приведет к ошибке:
# Runs fine
y = [i for i in range(x)]
Это относится только к самой внешней итерации; если у понимания есть несколько for
предложений, то итерации для внутренних for
предложений оцениваются в области понимания:
# NameError
y = [i for i in range(1) for j in range(x)]
Это проектное решение было принято для того, чтобы выдавать ошибку во время создания genexp вместо времени итерации, когда создание самой внешней итерируемой выражения-генератора генерирует ошибку, или когда самая внешняя итерация оказывается не повторяемой. Постижения разделяют это поведение для согласованности.
Заглядывая под капот; или, более подробно, чем вы когда-либо хотели
Вы можете увидеть все это в действии, используя dis
модуль . Я использую Python 3.3 в следующих примерах, потому что он добавляет квалифицированные имена, которые четко идентифицируют объекты кода, которые мы хотим проверить. Полученный байт-код функционально идентичен Python 3.2.
Чтобы создать класс, Python, по сути, берет весь набор, который составляет тело класса (так что все отступает на один уровень глубже, чем class <name>:
строка), и выполняет это, как если бы это была функция:
>>> import dis
>>> def foo():
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo)
2 0 LOAD_BUILD_CLASS
1 LOAD_CONST 1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>)
4 LOAD_CONST 2 ('Foo')
7 MAKE_FUNCTION 0
10 LOAD_CONST 2 ('Foo')
13 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
16 STORE_FAST 0 (Foo)
5 19 LOAD_FAST 0 (Foo)
22 RETURN_VALUE
Первый LOAD_CONST
загружает объект кода для Foo
тела класса, затем превращает его в функцию и вызывает его. Результат этого вызова используется для создания пространства имен класса, его __dict__
. Все идет нормально.
Здесь следует отметить, что байт-код содержит объект вложенного кода; в Python определения классов, функции, понимания и генераторы все представлены как объекты кода, которые содержат не только байт-код, но также и структуры, которые представляют локальные переменные, константы, переменные, взятые из глобальных переменных, и переменные, взятые из вложенной области видимости. Скомпилированный байт-код ссылается на эти структуры, и интерпретатор python знает, как получить доступ к тем, которые представлены представленными байт-кодами.
Важно помнить, что Python создает эти структуры во время компиляции; class
люкс код объекта ( <code object Foo at 0x10a436030, file "<stdin>", line 2>
) , который уже составлен.
Давайте проверим тот объект кода, который создает само тело класса; Объекты кода имеют co_consts
структуру:
>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
2 0 LOAD_FAST 0 (__locals__)
3 STORE_LOCALS
4 LOAD_NAME 0 (__name__)
7 STORE_NAME 1 (__module__)
10 LOAD_CONST 0 ('foo.<locals>.Foo')
13 STORE_NAME 2 (__qualname__)
3 16 LOAD_CONST 1 (5)
19 STORE_NAME 3 (x)
4 22 LOAD_CONST 2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>)
25 LOAD_CONST 3 ('foo.<locals>.Foo.<listcomp>')
28 MAKE_FUNCTION 0
31 LOAD_NAME 4 (range)
34 LOAD_CONST 4 (1)
37 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
40 GET_ITER
41 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
44 STORE_NAME 5 (y)
47 LOAD_CONST 5 (None)
50 RETURN_VALUE
Приведенный выше байт-код создает тело класса. Функция выполняется, и результирующее locals()
пространство имен, содержащее x
и y
используемое для создания класса (за исключением того, что оно не работает, потому что x
не определено как глобальное). Обратите внимание , что после хранения 5
в x
он загружает другой код объекта; это понимание списка; он обернут в объект функции так же, как тело класса; созданная функция принимает позиционный аргумент, range(1)
итеративный для использования в циклическом коде, приведенном к итератору. Как показано в байт-коде, range(1)
оценивается в области видимости класса.
Из этого вы можете видеть, что единственное различие между объектом кода для функции или генератора и объектом кода для понимания состоит в том, что последний выполняется сразу же, когда выполняется родительский объект кода; Байт-код просто создает функцию на лету и выполняет ее за несколько небольших шагов.
Вместо этого в Python 2.x используется встроенный байт-код, а здесь вывод из Python 2.7:
2 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
3 6 LOAD_CONST 0 (5)
9 STORE_NAME 2 (x)
4 12 BUILD_LIST 0
15 LOAD_NAME 3 (range)
18 LOAD_CONST 1 (1)
21 CALL_FUNCTION 1
24 GET_ITER
>> 25 FOR_ITER 12 (to 40)
28 STORE_NAME 4 (i)
31 LOAD_NAME 2 (x)
34 LIST_APPEND 2
37 JUMP_ABSOLUTE 25
>> 40 STORE_NAME 5 (y)
43 LOAD_LOCALS
44 RETURN_VALUE
Кодовый объект не загружается, вместо этого FOR_ITER
выполняется встроенный цикл. Таким образом, в Python 3.x генератору списков был предоставлен собственный объект кода, что означает, что он имеет собственную область видимости.
Однако понимание было скомпилировано вместе с остальной частью исходного кода Python, когда модуль или сценарий был впервые загружен интерпретатором, и компилятор не считает набор классов допустимой областью действия. Любые ссылочные переменные в понимании списка должны рекурсивно смотреть в области, окружающей определение класса. Если переменная не была найдена компилятором, она помечает ее как глобальную. Разборка объекта кода со списком показывает, что x
он действительно загружен как глобальный:
>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
4 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_GLOBAL 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Этот кусок байт-кода загружает первый переданный аргумент ( range(1)
итератор), и точно так же, как версия Python 2.x использует FOR_ITER
для его циклического перемещения и создания выходных данных.
Если бы мы определили x
в foo
функции вместо этого, x
была бы переменная ячейки (ячейки ссылаются на вложенные области видимости):
>>> def foo():
... x = 2
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
5 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
LOAD_DEREF
Косвенно загружать x
из объектов объектного кода ячейки:
>>> foo.__code__.co_cellvars # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars # Refers to `x` in foo
('x',)
>>> foo().y
[2]
Фактическая ссылка просматривает значение из структур данных текущего кадра, которые были инициализированы из .__closure__
атрибута функционального объекта . Поскольку функция, созданная для объекта кода понимания, снова отбрасывается, мы не можем проверить закрытие этой функции. Чтобы увидеть замыкание в действии, вместо этого мы должны были бы проверить вложенную функцию:
>>> def spam(x):
... def eggs():
... return x
... return eggs
...
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5
Итак, подведем итог:
- Постижения списков получают свои собственные объекты кода в Python 3, и нет разницы между объектами кода для функций, генераторов или пониманий; Объекты кода понимания заключаются во временный объект функции и вызываются немедленно.
- Объекты кода создаются во время компиляции, и любые нелокальные переменные помечаются как глобальные или как свободные переменные в зависимости от вложенных областей кода. Тело класса не считается областью для поиска этих переменных.
- При выполнении кода Python должен только смотреть на глобальные переменные или закрытие текущего выполняемого объекта. Поскольку компилятор не включил тело класса в качестве области видимости, пространство имен временной функции не рассматривается.
Обходной путь; или что с этим делать
Если вы хотите создать явную область видимости для x
переменной, как в функции, вы можете использовать переменные области видимости для понимания списка:
>>> class Foo:
... x = 5
... def y(x):
... return [x for i in range(1)]
... y = y(x)
...
>>> Foo.y
[5]
«Временная» y
функция может быть вызвана напрямую; мы заменяем его, когда делаем его возвращаемым значением. Его сфера будет учитываться при решении x
:
>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)
Конечно, люди, читающие ваш код, немного поцарапают голову над этим; Вы можете разместить большой жирный комментарий, объясняющий, почему вы это делаете.
Лучший обходной путь - просто использовать __init__
вместо этого создание переменной экземпляра:
def __init__(self):
self.y = [self.x for i in range(1)]
и избегать всех царапин головы и вопросов, чтобы объяснить себя. Для вашего собственного конкретного примера я бы даже не хранил namedtuple
в классе; либо используйте вывод напрямую (не храните сгенерированный класс вообще), либо используйте глобальный:
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])
class StateDatabase:
db = [State(*args) for args in [
('Alabama', 'Montgomery'),
('Alaska', 'Juneau'),
# ...
]]
NameError: global name 'x' is not defined
на Python 3.2 и 3.3, что я и ожидал.