Ответ 1
TL; DR. Такое поведение существует, поскольку Python 2.1 PEP 227: Вложенные области и был известен назад тогда. Если имя назначено внутри тела класса (например, y
), то предполагается, что оно является локальной/глобальной переменной; если он не назначен (x
), то он также может потенциально указывать на ячейку замыкания. Лексические переменные не отображаются как локальные/глобальные имена для тела класса.
В Python 3.4, dis.dis(func)
показано следующее:
>>> dis.dis(func)
4 0 LOAD_CONST 1 ('xlocal')
3 STORE_DEREF 0 (x)
5 6 LOAD_CONST 2 ('ylocal')
9 STORE_FAST 0 (y)
6 12 LOAD_BUILD_CLASS
13 LOAD_CLOSURE 0 (x)
16 BUILD_TUPLE 1
19 LOAD_CONST 3 (<code object C at 0x7f083c9bbf60, file "test.py", line 6>)
22 LOAD_CONST 4 ('C')
25 MAKE_CLOSURE 0
28 LOAD_CONST 4 ('C')
31 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
34 STORE_FAST 1 (C)
37 LOAD_CONST 0 (None)
40 RETURN_VALUE
LOAD_BUILD_CLASS
загружает builtins.__build_class__
в стек; это вызывается с аргументами __build_class__(func, name)
; где func
- тело класса, а name
- 'C'
. Тело класса является константой # 3 для функции func
:
>>> dis.dis(func.__code__.co_consts[3])
6 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
6 LOAD_CONST 0 ('func.<locals>.C')
9 STORE_NAME 2 (__qualname__)
7 12 LOAD_NAME 3 (print)
15 LOAD_CLASSDEREF 0 (x)
18 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
21 POP_TOP
8 22 LOAD_NAME 3 (print)
25 LOAD_NAME 4 (y)
28 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
31 POP_TOP
9 32 LOAD_CONST 1 (1)
35 STORE_NAME 4 (y)
38 LOAD_CONST 2 (None)
41 RETURN_VALUE
Внутри тела класса доступ к x
осуществляется с помощью LOAD_CLASSDEREF
(15), а y
- с нагрузкой LOAD_NAME
(25). LOAD_CLASSDEREF
представляет собой код операции Python 3.4+ для загрузки значений из закрывающих ячеек, конкретно в телах классов (в предыдущих версиях использовался общий LOAD_DEREF
); LOAD_NAME
предназначен для загрузки значений из локальных и глобальных переменных. Однако закрывающие ячейки не отображаются ни как локальные, ни глобальные.
Теперь, поскольку имя y
сохраняется внутри тела класса (35), оно последовательно используется как не ячейка замыкания, а локальное/глобальное имя.
Ячейки замыкания не отображаются в виде локальных переменных в тело класса.
Это было истинно с момента внедрения PEP 227 - вложенных областей. И тогда BDFL заявила, что это не должно быть исправлено - и, следовательно, это было за эти 13 лет.
Единственное изменение, поскольку PEP 227 является добавлением nonlocal
в Python 3; если он использует его внутри тела класса, тело класса может устанавливать значения ячеек в пределах области содержимого:
x = "xtop"
y = "ytop"
def func():
x = "xlocal"
y = "ylocal"
class C:
nonlocal y # y here now refers to the outer variable
print(x)
print(y)
y = 1
print(y)
print(C.y)
func()
Теперь выводится
xlocal
ylocal
1
Traceback (most recent call last):
File "test.py", line 15, in <module>
func()
File "test.py", line 13, in func
print(C.y)
AttributeError: type object 'C' has no attribute 'y'
То есть, print(y)
читает значение ячейки y
содержащейся области, а y = 1
устанавливает значение в этой ячейке; в этом случае для класса C
не был создан атрибут.