dunder __str__ __eq__ __dict__ python python-3.x closures python-datamodel

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 de super() 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.