Переменная Шредингера: ячейка __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()
правильно идентифицировать определяемый класс на основе лексической области видимости, в то время как класс или экземпляр, который использовался для выполнения текущего вызова, идентифицируется на основе первого аргумента, переданного методу.