Переменная Шредингера: ячейка __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Так почему же компилятор создает здесь неявную ссылку на замыкание, если она не нужна?

2 ответа

Решение

Это странное взаимодействие в реализации 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__ означает, что вы получаете класс вместо значения переменной включающей области видимости.

https://docs.python.org/3/reference/datamodel.html

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

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