Переменная Schrödinger: ячейка __class__ волшебно появляется, если вы проверяете ее присутствие?

Здесь сюрприз:

>>> class B:
...     print(locals())
...     def foo(self):
...         print(locals())
...         print(__class__ in locals().values())
...         
{'__module__': '__main__', '__qualname__': 'B'}
>>> B().foo()
{'__class__': <class '__main__.B'>, 'self': <__main__.B object at 0x7fffe916b4a8>}
True

Кажется, что простое упоминание __class__ явно проверяется парсером? В противном случае мы должны получить что-то вроде

NameError: name '__class__' is not defined

В самом деле, если вы модифицируете, чтобы проверить только ключ, т.е. проверить на '__class__' in locals(), тогда у нас есть только self в области видимости.

Как происходит, что эта переменная волшебным образом вводится в область видимости? Я предполагаю, что это как-то связано с super - но я не использовал super, поэтому почему компилятор создает ссылку на неявное замыкание здесь, если это не нужно?

Ответ 1

Это странное взаимодействие в реализации Python 3 без аргумента super. Доступ к super в методе запускает добавление скрытой переменной __class__ закрытия, относящейся к классу, который определяет метод. В синтаксическом анализаторе особый случай загружает имя super в методе, также добавляя __class__ в таблицу символов метода, а затем остальная часть соответствующего кода ищет __class__ вместо super. Однако, если вы попытаетесь получить доступ к __class__ самостоятельно, весь код, ищущий __class__, видит его и считает, что он должен выполнять обработку super!

Здесь, где он добавляет имя __class__ в таблицу символов, если видит super:

case Name_kind:
    if (!symtable_add_def(st, e->v.Name.id,
                          e->v.Name.ctx == Load ? USE : DEF_LOCAL))
        VISIT_QUIT(st, 0);
    /* Special-case super: it counts as a use of __class__ */
    if (e->v.Name.ctx == Load &&
        st->st_cur->ste_type == FunctionBlock &&
        !PyUnicode_CompareWithASCIIString(e->v.Name.id, "super")) {
        if (!GET_IDENTIFIER(__class__) ||
            !symtable_add_def(st, __class__, USE))
            VISIT_QUIT(st, 0);
    }
    break;

Здесь drop_class_free, который устанавливает ste_needs_class_closure:

static int
drop_class_free(PySTEntryObject *ste, PyObject *free)
{
    int res;
    if (!GET_IDENTIFIER(__class__))
        return 0;
    res = PySet_Discard(free, __class__);
    if (res < 0)
        return 0;
    if (res)
        ste->ste_needs_class_closure = 1;
    return 1;
}

Раздел компилятора, который проверяет ste_needs_class_closure и создает неявную ячейку:

if (u->u_ste->ste_needs_class_closure) {
    /* Cook up an implicit __class__ cell. */
    _Py_IDENTIFIER(__class__);
    PyObject *tuple, *name, *zero;
    int res;
    assert(u->u_scope_type == COMPILER_SCOPE_CLASS);
    assert(PyDict_Size(u->u_cellvars) == 0);
    name = _PyUnicode_FromId(&PyId___class__);
    if (!name) {
        compiler_unit_free(u);
        return 0;
    }
    ...

Там более релевантный код, но он слишком много, чтобы включить его все. Python/compile.c и Python/symtable.c должны посмотреть, хотите ли вы видеть больше.

Вы можете получить некоторые странные ошибки, если попытаетесь использовать переменную с именем __class__:

class Foo:
    def f(self):
        __class__ = 3
        super()

Foo().f()

Вывод:

Traceback (most recent call last):
  File "./prog.py", line 6, in <module>
  File "./prog.py", line 4, in f
RuntimeError: super(): __class__ cell not found

Назначение __class__ означает __class__ - это локальная переменная вместо переменной замыкания, поэтому закрывающая ячейка super() не нужна.

def f():
    __class__ = 2
    class Foo:
        def f(self):
            print(__class__)

    Foo().f()

f()

Вывод:

<class '__main__.f.<locals>.Foo'>

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

Ответ 2

https://docs.python.org/3/reference/datamodel.html#creating-the-class-object

__class__ - это неявная ссылка на замыкание, созданная компилятором, если любые методы в классе класса относятся либо к __class__, либо к супер. Это позволяет форме нулевого аргумента super() правильно идентифицировать определяемый класс на основе лексического охвата, тогда как класс или экземпляр, который использовался для создания текущего вызова, идентифицируется на основе первого аргумента, переданного методу.