Python: какие области доступны при определении переменных класса?

Рассмотрим эти два файла Python:

# file1.py
global_var = "abc"

class A:
    x = 1
    glb = global_var
    y = x + 1
    class B:
        z = 3
        glb = global_var
    zz = B.z

print(f"{A.B.z=}")
print(f"{A.zz=}")
# file2.py
global_var = "abc"

class A:
    x = 1
    glb = global_var
    y = x + 1
    class B:
        z = y + 1
        glb = global_var
    zz = B.z

print(f"{A.B.z=}")
print(f"{A.zz=}")

Можно было бы ожидать, что они сделают то же самое. Но это не так!

$ python file1.py
A.B.z=3
A.zz=3
$ python file2.py
Traceback (most recent call last):
  File "file2.py", line 4, in <module>
    class A:
  File "file2.py", line 8, in A
    class B:
  File "file2.py", line 9, in B
    z = y + 1
NameError: name 'y' is not defined

Вопросы:

  • Почему определение B может получить доступ к глобальной области, но не к области A?
  • Почему y = x + 1 должно работать, а z = y + 1 нет? Это дизайнерское решение или неопределенное поведение CPython?
  • Каковы общие правила для того, какие переменные/область доступны при вычислении значений переменных класса? Когда я должен беспокоиться о том, какую область мне разрешено использовать при определении переменных моего класса?

Это происходит по той же причине, по которой вы должны использовать self.x или ClassName.x для доступа к переменной класса x в методе.

user2357112 17.12.2020 22:18

Я верю (забудьте, где это задокументировано), что все поиски в операторе class происходят в глобальной области, а не в ближайшей содержащей области. (В случае B глобальная область в любом случае является ближайшей содержащей область, потому что оператор class не создает новую область.) y не определен, потому что определение B не смотрит в определение A.

chepner 17.12.2020 22:21

@chepner Это то, чего я ожидал, но печать locals().keys() в определениях A и B действительно показывает соответствующие переменные класса в локальной области видимости. И на самом деле, если подумать, так и должно быть, иначе все переменные класса оказались бы в области видимости модуля. Таким образом, область класса есть, но это временная область, которая существует только при построении класса.

zvone 17.12.2020 22:24

Нет, есть пространство имен классов; это не то же самое, что масштаб. locals возвращает это пространство имен, но это не означает, что разрешение имен использует его.

chepner 17.12.2020 22:36

Соответствующая часть из PEP 227: Имена в области класса недоступны. Имена разрешаются в самой внутренней области объемлющей функции. Если определение класса встречается в цепочке вложенных областей, процесс разрешения пропускает определения классов.

wim 17.12.2020 22:51
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
6
243
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Из https://docs.python.org/3/reference/executionmodel.html:

Блоки определения класса и аргументы для exec() и eval() имеют особое значение в контексте разрешения имен. Определение класса — это исполняемый оператор, который может использовать и определять имена. Эти ссылки следуют обычным правилам разрешения имен, за исключением того, что несвязанные локальные переменные просматриваются в глобальном пространстве имен. Пространство имен определения класса становится словарем атрибутов класса. Объем имен, определенных в блоке класса, ограничен блоком класса; он не распространяется на блоки кода методов — это включает в себя включения и выражения генератора, поскольку они реализованы с использованием области действия функции. Это означает, что следующее не удастся:

В определении By является несвязанной локальной переменной, поэтому поиск осуществляется в глобальной области видимости (где она не определена), а не в пространстве имен, созданном прилагаемым оператором class.

Оператор class вообще не определяет область действия; он создает пространство имен, которое передается метаклассу для создания нового класса.

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

user2357112 17.12.2020 22:35

В вашем примере x не является несвязанной локальной переменной, я думаю, из-за закрытия.

chepner 17.12.2020 22:38

Замыкания находятся в функциях, а не в классах

Federico Baù 17.12.2020 22:42

Ответы:

  • Почему определение B может получить доступ к глобальной области, но не к области A?

Чепнер Уже ответил на этот вопрос, но вкратце: локальные переменные просматриваются в глобальном пространстве имен, поэтому способ вложения классов в ваш пример кода работает, как и ожидалось.

  • Почему y = x + 1 должно работать, а z = y + 1 — нет? Это дизайнерское решение или неопределенное поведение CPython?

Он работает так, как ожидалось, как указано выше.


  • Каковы общие правила для того, какие переменные/область доступны при вычислении значений переменных класса? Когда я должен беспокоиться о том, какую область мне разрешено использовать при определении переменных моего класса?

Они не работают, как для вложенных функций с правилом LEGB /realpython:

«Когда вы определяете класс, вы создаете новую локальную область Python. Имена, назначенные на верхнем уровне класса, живут в этой локальной области. Имена, которые вы назначаете внутри оператора класса, не конфликтуют с именами в других местах. Можно сказать, что эти имена следуют правилу LEGB, где блок класса представляет уровень L».

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

class B(A):
    y = A.y
    z =  y  + 1
    glb = global_var

Другие вопросы по теме