Как вы получаете доступ к другим переменным класса из понимания списка в определении класса? Следующее работает в Python 2, но не работает в Python 3:
class Foo:
x = 5
y = [x for i in range(1)]
Python 3.2 выдает ошибку:
NameError: global name 'x' is not defined
Попытка Foo.x
тоже не работает. Любые идеи о том, как сделать это в Python 3?
Немного более сложный мотивирующий пример:
from collections import namedtuple
class StateDatabase:
State = namedtuple('State', ['name', 'capital'])
db = [State(*args) for args in [
['Alabama', 'Montgomery'],
['Alaska', 'Juneau'],
# ...
]]
В этом примере apply()
это был бы достойный обходной путь, но он, к сожалению, удален из Python 3.
python
python-3.x
scope
list-comprehension
python-internals
Марк Лодато
источник
источник
NameError: global name 'x' is not defined
на Python 3.2 и 3.3, что я и ожидал.Ответы:
Область видимости и список классов, набор или словарь, а также выражения генератора не смешиваются.
Почему; или официальное слово об этом
В Python 3 для списочных представлений была назначена собственная область видимости (локальное пространство имен), чтобы их локальные переменные не могли перетекать в окружающую область видимости (см. Перечень пониманий списков Python связывает имена даже после объема понимания. Это правильно? ). Это замечательно, если использовать такое понимание списка в модуле или в функции, но в классах определение области видимости немного странно .
Это задокументировано в ОП 227 :
и в
class
документации составного заявления :Акцент мой; фрейм выполнения - это временная область.
Поскольку область видимости повторно используется в качестве атрибутов объекта класса, что позволяет использовать ее как нелокальную область действия, что ведет к неопределенному поведению; что произойдет, если метод класса, называемый
x
вложенной областью видимости, затем также манипулируетFoo.x
, например? Что еще более важно, что бы это значило для подклассовFoo
? Python должен относиться к области видимости класса по-другому, поскольку она сильно отличается от области видимости функции.Наконец, но не в последнюю очередь, в связанном разделе « Именование и привязка » в документации по модели выполнения явно упоминаются области действия классов:
Итак, подведем итог: вы не можете получить доступ к области видимости класса из функций, списков или выражений генератора, заключенных в эту область; они действуют так, как будто эта область не существует. В Python 2, списочные понимания были реализованы с помощью ярлыка, но в Python 3 они получили свою собственную область действия функций (как и следовало иметь), и поэтому ваш пример ломается. Другие типы понимания имеют свою собственную область видимости независимо от версии Python, поэтому аналогичный пример с пониманием set или dict сломался бы в Python 2.
(Небольшое) исключение; или почему одна часть все еще может работать
Есть одна часть выражения понимания или генератора, которая выполняется в окружающей области, независимо от версии Python. Это было бы выражением для самой внешней итерации. В вашем примере это
range(1)
:Таким образом, использование
x
в этом выражении не приведет к ошибке:Это относится только к самой внешней итерации; если у понимания есть несколько
for
предложений, то итерации для внутреннихfor
предложений оцениваются в области понимания:Это проектное решение было принято для того, чтобы выдавать ошибку во время создания genexp вместо времени итерации, когда создание самой внешней итерируемой выражения-генератора генерирует ошибку, или когда самая внешняя итерация оказывается не повторяемой. Постижения разделяют это поведение для согласованности.
Заглядывая под капот; или, более подробно, чем вы когда-либо хотели
Вы можете увидеть все это в действии, используя
dis
модуль . Я использую Python 3.3 в следующих примерах, потому что он добавляет квалифицированные имена, которые четко идентифицируют объекты кода, которые мы хотим проверить. Полученный байт-код функционально идентичен Python 3.2.Чтобы создать класс, Python, по сути, берет весь набор, который составляет тело класса (так что все отступает на один уровень глубже, чем
class <name>:
строка), и выполняет это, как если бы это была функция:Первый
LOAD_CONST
загружает объект кода дляFoo
тела класса, затем превращает его в функцию и вызывает его. Результат этого вызова используется для создания пространства имен класса, его__dict__
. Все идет нормально.Здесь следует отметить, что байт-код содержит объект вложенного кода; в Python определения классов, функции, понимания и генераторы все представлены как объекты кода, которые содержат не только байт-код, но также и структуры, которые представляют локальные переменные, константы, переменные, взятые из глобальных переменных, и переменные, взятые из вложенной области видимости. Скомпилированный байт-код ссылается на эти структуры, и интерпретатор python знает, как получить доступ к тем, которые представлены представленными байт-кодами.
Важно помнить, что Python создает эти структуры во время компиляции;
class
люкс код объекта (<code object Foo at 0x10a436030, file "<stdin>", line 2>
) , который уже составлен.Давайте проверим тот объект кода, который создает само тело класса; Объекты кода имеют
co_consts
структуру:Приведенный выше байт-код создает тело класса. Функция выполняется, и результирующее
locals()
пространство имен, содержащееx
иy
используемое для создания класса (за исключением того, что оно не работает, потому чтоx
не определено как глобальное). Обратите внимание , что после хранения5
вx
он загружает другой код объекта; это понимание списка; он обернут в объект функции так же, как тело класса; созданная функция принимает позиционный аргумент,range(1)
итеративный для использования в циклическом коде, приведенном к итератору. Как показано в байт-коде,range(1)
оценивается в области видимости класса.Из этого вы можете видеть, что единственное различие между объектом кода для функции или генератора и объектом кода для понимания состоит в том, что последний выполняется сразу же, когда выполняется родительский объект кода; Байт-код просто создает функцию на лету и выполняет ее за несколько небольших шагов.
Вместо этого в Python 2.x используется встроенный байт-код, а здесь вывод из Python 2.7:
Кодовый объект не загружается, вместо этого
FOR_ITER
выполняется встроенный цикл. Таким образом, в Python 3.x генератору списков был предоставлен собственный объект кода, что означает, что он имеет собственную область видимости.Однако понимание было скомпилировано вместе с остальной частью исходного кода Python, когда модуль или сценарий был впервые загружен интерпретатором, и компилятор не считает набор классов допустимой областью действия. Любые ссылочные переменные в понимании списка должны рекурсивно смотреть в области, окружающей определение класса. Если переменная не была найдена компилятором, она помечает ее как глобальную. Разборка объекта кода со списком показывает, что
x
он действительно загружен как глобальный:Этот кусок байт-кода загружает первый переданный аргумент (
range(1)
итератор), и точно так же, как версия Python 2.x используетFOR_ITER
для его циклического перемещения и создания выходных данных.Если бы мы определили
x
вfoo
функции вместо этого,x
была бы переменная ячейки (ячейки ссылаются на вложенные области видимости):LOAD_DEREF
Косвенно загружатьx
из объектов объектного кода ячейки:Фактическая ссылка просматривает значение из структур данных текущего кадра, которые были инициализированы из
.__closure__
атрибута функционального объекта . Поскольку функция, созданная для объекта кода понимания, снова отбрасывается, мы не можем проверить закрытие этой функции. Чтобы увидеть замыкание в действии, вместо этого мы должны были бы проверить вложенную функцию:Итак, подведем итог:
Обходной путь; или что с этим делать
Если вы хотите создать явную область видимости для
x
переменной, как в функции, вы можете использовать переменные области видимости для понимания списка:«Временная»
y
функция может быть вызвана напрямую; мы заменяем его, когда делаем его возвращаемым значением. Его сфера будет учитываться при решенииx
:Конечно, люди, читающие ваш код, немного поцарапают голову над этим; Вы можете разместить большой жирный комментарий, объясняющий, почему вы это делаете.
Лучший обходной путь - просто использовать
__init__
вместо этого создание переменной экземпляра:и избегать всех царапин головы и вопросов, чтобы объяснить себя. Для вашего собственного конкретного примера я бы даже не хранил
namedtuple
в классе; либо используйте вывод напрямую (не храните сгенерированный класс вообще), либо используйте глобальный:источник
y = (lambda x=x: [x for i in range(1)])()
lambda
это просто анонимные функции.На мой взгляд, это недостаток в Python 3. Надеюсь, они его поменяют.
Старый путь (работает в 2.7, бросает
NameError: name 'x' is not defined
в 3+):ПРИМЕЧАНИЕ: простое определение области действия
A.x
не решит проблемуНовый путь (работает в 3+):
Поскольку синтаксис настолько ужасен, я просто инициализирую все свои переменные класса в конструкторе.
источник
def
для создания функции).python -c "import IPython;IPython.embed()"
. Запустите IPython напрямую, используя say,ipython
и проблема исчезнет.Принятый ответ дает отличную информацию, но здесь, по-видимому, есть еще несколько складок - различия между пониманием списка и выражениями генератора. Демо, с которым я играл:
источник
Это ошибка в Python. Понимания рекламируются как эквивалентные циклам for, но в классах это не так. По крайней мере, до Python 3.6.6, в понимании, используемом в классе, внутри понимания доступна только одна переменная снаружи понимания, и она должна использоваться как самый внешний итератор. В функции это ограничение области применения не применяется.
Чтобы проиллюстрировать, почему это ошибка, давайте вернемся к исходному примеру. Это не удается:
Но это работает:
Ограничение указано в конце этого раздела в справочном руководстве.
источник
Поскольку внешний итератор оценивается в окружающей области, которую мы можем использовать
zip
вместе сitertools.repeat
переносом зависимостей в область понимания:Можно также использовать вложенные
for
циклы в понимании и включать зависимости во внешнюю итерацию:Для конкретного примера OP:
источник