¿Cómo hacer que funcione super () llenando manualmente la celda __class__?

En Python 3, se puede usar super() lugar de super(MyClass, self) , pero esto solo funciona en métodos que se definieron dentro de la clase. Como se describe en el artículo de Michele Simionato, el siguiente ejemplo no funciona:

 def __init__(self): print('calling __init__') super().__init__() class C(object): __init__ = __init__ if __name__ == '__main__': c = C() 

Falla porque super() busca una celda __class__ , que no está definida en este caso.

¿Es posible configurar esta celda manualmente después de que se haya definido la función, o es imposible?

Desafortunadamente, no entiendo cómo funcionan las células en este contexto (no encontré mucha documentación para eso). Estoy esperando algo como

 __init__.__class_cell_thingy__ = C 

Por supuesto, solo usaría esto en una situación en la que la asignación de clase no sea ambigua / única (todo el proceso de agregar métodos a una clase se automatiza en mi caso, por lo que sería sencillo agregar una línea de este tipo).

En serio: realmente no quieres hacer esto.

Pero es útil para los usuarios avanzados de Python entender esto, así que lo explicaré.

Las celdas y barras son los valores asignados cuando se crea un cierre. Por ejemplo,

 def f(): a = 1 def func(): print(a) return func 

f devuelve un cierre basado en func , almacenando una referencia a a . Esa referencia se almacena en una celda (en realidad en un Freevar, pero cuál es la implementación). Puedes examinar esto:

 myfunc = f() # ('a',) print(myfunc.__code__.co_freevars) # (,) print(myfunc.__closure__) 

(“celdas” y “freevars” son muy similares. Los freevars tienen nombres, donde las celdas tienen índices. Ambos están almacenados en la func.__closure__ , con celdas primero. Nos importan los freevars , ya que eso es lo que __class__ es.)

Una vez que entiendas eso, puedes ver cómo funciona realmente super (). Cualquier función que contenga una llamada a super es en realidad un cierre, con un botón de acceso llamado __class__ (que también se agrega si usted se refiere a __class__ ):

 class foo: def bar(self): print(__class__) 

(Advertencia: aquí es donde las cosas se ponen mal.)

Estas celdas son visibles en func.__closure__ , pero es de solo lectura; no puedes cambiarlo La única forma de cambiarlo es crear una nueva función, que se realiza con el constructor types.FunctionType . Sin embargo, su función __init__ no tiene un __class__ freevar en absoluto, por lo que necesitamos agregar uno. Eso significa que también tenemos que crear un nuevo objeto de código.

El siguiente código hace esto. Agregué una clase base B para propósitos demostrativos. Este código hace algunas suposiciones, por ejemplo. que __init__ no tiene una variable libre llamada __class__ .

Hay otro truco aquí: no parece haber un constructor para el tipo de celda. Para evitar esto, se crea una función ficticia C.dummy que tiene la variable de celda que necesitamos.

 import types class B(object): def __init__(self): print("base") class C(B): def dummy(self): __class__ def __init__(self): print('calling __init__') super().__init__() def MakeCodeObjectWithClass(c): """ Return a copy of the code object c, with __class__ added to the end of co_freevars. """ return types.CodeType(c.co_argcount, c.co_kwonlyargcount, c.co_nlocals, c.co_stacksize, c.co_flags, c.co_code, c.co_consts, c.co_names, c.co_varnames, c.co_filename, c.co_name, c.co_firstlineno, c.co_lnotab, c.co_freevars + ('__class__',), c.co_cellvars) new_code = MakeCodeObjectWithClass(__init__.__code__) old_closure = __init__.__closure__ or () C.__init__ = types.FunctionType(new_code, globals(), __init__.__name__, __init__.__defaults__, old_closure + (C.dummy.__closure__[0],)) if __name__ == '__main__': c = C() 

Puedes usar el diccionario de la función.

 def f(self): super(f.owner_cls, self).f() print("B") def add_to_class(cls, member, name=None): if hasattr(member, 'owner_cls'): raise ValueError("%r already added to class %r" % (member, member.owner_cls)) member.owner_cls = cls if name is None: name = member.__name__ setattr(cls, name, member) class A: def f(self): print("A") class B(A): pass add_to_class(B, f) B().f() 

Incluso puede agregar otro atributo member_name si no quiere codificar el nombre del miembro dentro de la función.

Tal vez, pero ¿por qué lo harías? En ambos casos, debe ser explícito de qué clase es, porque la forma implícita no funcionó. Tal vez pueda configurar la celda de alguna manera explícita, pero no hay razón para hacerlo. Simplemente pase los parámetros explícitamente.

 def __init__(self): print('calling __init__') super(self.__class__, self).__init__() class C(object): __init__ = __init__ if __name__ == '__main__': c = C() 

(Es mejor si puedes pasar la clase real directamente, así:

 def __init__(self): print('calling __init__') super(C, self).__init__() class C(object): __init__ = __init__ if __name__ == '__main__': c = C() 

Pero si puedes, puedes poner el __init__ en C directamente, así que asume que no puedes.