Подтвердить что ты не робот

Правила определения класса класса Python

EDIT: Похоже, что это очень старая "ошибка" или, фактически, функция. См., Например, эту почту

Я пытаюсь понять правила обзора Python. Точнее, я думал, что я их понимаю, но затем я нашел этот код здесь:

x = "xtop"
y = "ytop"
def func():
    x = "xlocal"
    y = "ylocal"
    class C:
        print(x)
        print(y)
        y = 1
func()

В Python 3.4 вывод:

xlocal
ytop

Если я заменил внутренний класс на функцию, тогда он разумно дает UnboundLocalError. Не могли бы вы объяснить мне, почему это ведет себя странно с классами и в чем причина такого выбора правил определения области видимости?

4b9b3361

Ответ 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 не был создан атрибут.

Ответ 2

Сначала сосредоточимся на случае замыкания - функция внутри функции:

x = "xtop"
y = "ytop"
def func():
    x = "xlocal"
    y = "ylocal"
    def inner():
 #       global y
        print(x)
        print(y)
        y='inner y'
        print(y)
    inner()  

Обратите внимание на прокомментированный global в inner Если вы запустите это, он реплицирует UnboundLocalError, который вы получили. Зачем?

Запустите dis.dis на нем:

>>> import dis
>>> dis.dis(func)
  6           0 LOAD_CONST               1 ('xlocal')
              3 STORE_DEREF              0 (x)

  7           6 LOAD_CONST               2 ('ylocal')
              9 STORE_FAST               0 (y)

  8          12 LOAD_CLOSURE             0 (x)
             15 BUILD_TUPLE              1
             18 LOAD_CONST               3 (<code object inner at 0x101500270, file "Untitled 3.py", line 8>)
             21 LOAD_CONST               4 ('func.<locals>.inner')
             24 MAKE_CLOSURE             0
             27 STORE_FAST               1 (inner)

 14          30 LOAD_FAST                1 (inner)
             33 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             36 POP_TOP
             37 LOAD_CONST               0 (None)
             40 RETURN_VALUE

Обратите внимание на другой режим доступа x vs y внутри func. Использование y='inner y' внутри inner создало UnboundLocalError

Теперь раскомментируйте global y внутри inner. Теперь вы однозначно создаете y как верхнюю глобальную версию, пока не смиритесь как y='inner y'

С global без комментирования печатает:

xlocal
ytop
inner y

Вы можете получить более разумный результат:

x = "xtop"
y = "ytop"
def func():
    global y, x
    print(x,y)
    x = "xlocal"
    y = "ylocal"
    def inner():
        global y
        print(x,y)
        y = 'inner y'
        print(x,y)
    inner()    

Печать

xtop ytop
xlocal ylocal
xlocal inner y

Анализ класса замыкания усложняется экземпляром переменных класса vs и что/когда выполняется голый класс (без экземпляра).

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

"Исправить" одно и то же: используйте ключевое слово global:

x = "xtop"
y = "ytop"
def func():
    global x, y
    x = "xlocal"
    y = "ylocal"
    class Inner:
        print(x, y)
        y = 'Inner_y'
        print(x, y) 

Печать

xlocal ylocal
xlocal Inner_y

Вы можете узнать больше о правилах области Python 3 в PEP 3104