python - __str__ - Variable de Schrödinger: ¿aparece la celda__class__ mágicamente si está comprobando su presencia?
__str__ python (2)
Esta es una interacción extraña en la implementación de Python 3 del super
sin argumentos. Un acceso a super
en un método activa la adición de una variable de cierre __class__
oculta que se refiere a la clase que define el método. El analizador especial-casos de una carga del nombre super
en un método también agregando __class__
a la tabla de símbolos del método, y luego el resto del código relevante busca __class__
lugar de super
. Sin embargo, si intenta acceder a __class__
usted mismo, todo el código que busca __class__
ve y cree que debería hacerlo de forma __class__
.
Aquí es donde se agrega el nombre __class__
a la tabla de símbolos si ve 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;
Aquí está drop_class_free
, que establece 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;
}
La sección del compilador que verifica ste_needs_class_closure
y crea la celda implícita:
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;
}
...
Hay un código más relevante, pero es demasiado para incluirlo todo. Python/compile.c
y Python/symtable.c
son dónde buscar si desea ver más.
Puedes obtener algunos errores extraños si intentas usar una variable llamada __class__
:
class Foo:
def f(self):
__class__ = 3
super()
Foo().f()
Salida:
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
La asignación a __class__
significa que __class__
es una variable local en lugar de una variable de cierre, por lo que la celda de cierre que super()
necesita.
def f():
__class__ = 2
class Foo:
def f(self):
print(__class__)
Foo().f()
f()
Salida:
<class ''__main__.f.<locals>.Foo''>
A pesar de que hay una variable real __class__
en el ámbito adjunto, la carcasa especial de __class__
significa que se obtiene la clase en lugar del valor variable del ámbito adjunto.
Hay una sorpresa aquí:
>>> 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
Parece que la mera mención de __class__
está verificada explícitamente por el analizador? De lo contrario deberíamos conseguir algo como
NameError: name ''__class__'' is not defined
De hecho, si modifica para que solo verifique la clave en su lugar, es decir, verifique ''__class__'' in locals()
, entonces solo tenemos self
alcance propio como se esperaba.
¿Cómo es que esta variable se inyecta mágicamente en el alcance? Supongo que esto tiene algo que ver con super
, pero no utilicé super
, así que ¿por qué el compilador crea una referencia de cierre implícita aquí si no es necesario?
https://docs.python.org/3/reference/datamodel.html#creating-the-class-object
__class__
es una referencia de cierre implícita creada por el compilador si alguno de los métodos en el cuerpo de una clase se refiere a__class__
o super. Esto permite que la forma de argumento cero desuper()
identifique correctamente la clase que se está definiendo en función del alcance léxico, mientras que la clase o instancia que se usó para realizar la llamada actual se identifica en función del primer argumento que se pasó al método.