Почему одна переменная класса не определена в понимании списка, а другая?

Я только что прочитал ответ на этот вопрос: Доступ к переменным класса из понимания списка в определении класса

Это помогает мне понять, почему следующий код приводит к NameError: name 'x' is not defined:

class A:
    x = 1
    data = [0, 1, 2, 3]
    new_data = [i + x for i in data]
    print(new_data)

NameError возникает из-за того, что x не определено в специальной области для понимания списка. Но я не могу понять, почему следующий код работает без ошибок.

class A:
    x = 1
    data = [0, 1, 2, 3]
    new_data = [i for i in data]
    print(new_data)

Я получаю вывод [0, 1, 2, 3]. Но я ожидал эту ошибку: NameError: name 'data' is not defined, потому что я ожидал, как и в предыдущем примере, что имя x не определено в области понимания списка, аналогично, имя data также не будет определено в области понимания списка.

Не могли бы вы помочь мне понять, почему x не определено в области понимания списка, а data есть?


person Lone Learner    schedule 27.03.2014    source источник


Ответы (2)


data — это источник понимания списка; это единственный параметр, который передается созданной вложенной области.

Все в понимании списка выполняется в отдельной области (в основном как функция), за исключением итерации, используемой для самого левого цикла for. Вы можете увидеть это в байт-коде:

>>> def foo():
...     return [i for i in data]
... 
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (<code object <listcomp> at 0x105390390, file "<stdin>", line 2>)
              3 LOAD_CONST               2 ('foo.<locals>.<listcomp>')
              6 MAKE_FUNCTION            0
              9 LOAD_GLOBAL              0 (data)
             12 GET_ITER
             13 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             16 RETURN_VALUE

Объект кода <listcomp> вызывается как функция, а iter(data) передается в качестве аргумента (CALL_FUNCTION выполняется с 1 позиционным аргументом, результатом GET_ITER).

Объект кода <listcomp> ищет этот единственный аргумент:

>>> dis.dis(foo.__code__.co_consts[1])
  2           0 BUILD_LIST               0
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                12 (to 21)
              9 STORE_FAST               1 (i)
             12 LOAD_FAST                1 (i)
             15 LIST_APPEND              2
             18 JUMP_ABSOLUTE            6
        >>   21 RETURN_VALUE

Вызов LOAD_FAST относится к первому и единственному переданному позиционному аргументу; здесь он безымянный, потому что никогда не было определения функции, чтобы дать ему имя.

Любые дополнительные имена, используемые в понимании списка (или понимании набора или словаря, или выражении генератора, если на то пошло), являются либо локальными, замыканиями, либо глобальными, а не параметрами.

Если вы вернетесь к моему ответу на этот вопрос, найдите раздел под названием Исключение (небольшое); или почему одна часть все еще может работать; Я попытался охватить этот конкретный момент:

Есть одна часть выражения понимания или генератора, которая выполняется в окружающей области, независимо от версии Python. Это будет выражение для самой внешней итерации.

person Martijn Pieters    schedule 27.03.2014
comment
Это хороший ответ, но он разбирает функцию, а не класс. Это не имеет смысла для класса... Разве x не является локальной переменной в области A? - person fmv1992; 23.03.2021
comment
@ fmv1992: ОП ссылается на вопрос, на который я ответил, о том, как это работает в классах. Этот ответ нужно читать в дополнение к моему ответу там, поэтому я не видел смысла повторяться. Разборка здесь иллюстрирует, почему data работает. Я включил разборку, чтобы показать, как реализовано понимание списка, чтобы показать, почему data не считается переменной в родительской области. Разборка одинакова вне зависимости от области применения, и смысл был в том, чтобы показать, как это работает под капотом. - person Martijn Pieters; 23.03.2021

Ответ dis.dis интересен, но на самом деле он не объясняет, почему это происходит. Вот это из похожей ошибки:

Если операция привязки имени происходит где-либо внутри блока кода, все случаи использования имени внутри блока рассматриваются как ссылки на текущий блок. Это может привести к ошибкам, когда имя используется в блоке до его привязки. Это правило тонкое. Python не имеет объявлений и позволяет выполнять операции привязки имен в любом месте блока кода. Локальные переменные блока кода можно определить путем сканирования всего текста блока на наличие операций привязки имен.

Проще говоря: data не может ссылаться на x, потому что блок не связан этой точкой. Невозможно сослаться на x: ни через x, ни через A.x.

Источник: python docs.

person fmv1992    schedule 23.03.2021
comment
Почему это происходит, уже ответили в вопросе, на который ссылается ОП. Обратите внимание, что это ответ, который я написал: Доступ к переменным класса из понимания списка в определении класса; Я освещаю ту же проблему там: Все в генерировании списка выполняется в отдельной области, за исключением источника генерирования списка. - person Martijn Pieters; 23.03.2021
comment
Что еще более важно, ОП спрашивает, почему это не относится к data, а не к x. И причина в том, что результат оценки выражения data, переданного в iter(), передается в «функцию», которая используется для реализации области понимания списка, и поэтому не подчиняется правилам, касающимся родительских областей. - person Martijn Pieters; 23.03.2021
comment
другого вопроса не увидел. Я считаю, что было бы лучше, если бы ответы были самодостаточными, иначе мы переходим от вопроса А к Б и С... Но приятно, спасибо за разъяснение. - person fmv1992; 23.03.2021
comment
Я твердо верю, что мой ответ самостоятелен, он охватывает конкретный вопрос, заданный здесь. Я дал вам контекст, который вы, возможно, пропустили, в вопросе. - person Martijn Pieters; 23.03.2021