2011-02-03 16 views
8

En Python 3 se puede usar super() en lugar de super(MyClass, self), pero esto solo funciona en métodos que se definieron dentro de la clase. Como se describe en el siguiente ejemplo Michele Simionato's article no funciona:¿Cómo hacer que super() funcione rellenando manualmente la celda __class__?

def __init__(self): 
    print('calling __init__') 
    super().__init__() 

class C(object): 
    __init__ = __init__ 

if __name__ == '__main__': 
    c = C() 

falla porque super() busca una __class__cell, que no se define 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 por algo así como

__init__.__class_cell_thingy__ = C 

Por supuesto, sólo lo usaría esto en una situación en la asignación de clase no es ambigua/único (todo el proceso de añadir métodos a una clase en la que se automatizado en mi caso, por lo sería simple agregar tal línea).

+3

"¿Es posible configurar esta celda manualmente?" Cuando probaste esto, ¿qué pasó? –

+0

Pero, ¿cómo puedo obtener acceso a esta celda después de definir la función? '__init __.__ class__' es, por supuesto, algo más. Ahora he editado la pregunta para aclarar esto. – nikow

+0

¿Por qué quieres definir la función fuera de la clase? Querrá esto si quiere reutilizar una función, o usar una función como método varias veces. No puedes hacer esas cosas y usar 'super (...)' correctamente. Y si está modificando una clase en tiempo de ejecución, simplemente puede codificarla. –

Respuesta

15

En serio: realmente no quiere hacer esto.

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

Las celdas y los freevars 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, almacenar una referencia a a. Esa referencia se almacena en una celda (en realidad en una versión libre, pero cuál corresponde a la implementación). Puede examinar esto:...

myfunc = f() 
# ('a',) 
print(myfunc.__code__.co_freevars) 
# (<cell at 0xb7abce84: int object at 0x82b1de0>,) 
print(myfunc.__closure__) 

("células" y "freevars" son muy similares Freevars tienen nombres, donde las células tienen índices Los dos son almacenados en func.__closure__, con células procedentes primera sólo nos interesan freevars aquí, ya que eso es lo que __class__ es.)

Una vez que comprenda eso, podrá ver cómo funciona super(). Cualquier función que contiene una llamada a super es en realidad un cierre, con un freevar llamado __class__ (que también se añade si se refiere a __class__ mismo):

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 ningún __class__ freevar, por lo que debemos agregar una. Eso significa que tenemos que crear un nuevo objeto de código también.

El siguiente código hace esto. Agregué una clase base B con fines demostrativos. Este código hace algunas suposiciones, ej. que __init__ aún no tiene una variable gratuita llamada __class__.

Hay otro truco aquí: no parece haber un constructor para el tipo de celda. Para solucionarlo, 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() 
+0

Gracias, excelente respuesta. Es bueno saber cómo funcionan estas cosas. Creo que tomaré su consejo y me atengo a las llamadas explícitas 'super' por el momento. Hasta que Python 3 se convierta en predominante, de todos modos es algo académico. – nikow

+0

P.S. Parece que esta respuesta lo empujó a 10k rep :-) – nikow

+1

Otro truco es definir una función dentro de otra función con un solo argumento '__class__' y luego devolver la función interna (o solo su cierre). Entonces no necesitarías el método ficticio allí para crear el cierre, o bien podrías definir el método real como tal función interna. Otra cosa es que podrías usar un contenedor de clase (probablemente con una metaclase auxiliar con métodos que oculten la magia) para almacenar las funciones, entonces no tendrías que crear objetos de código personalizados con esa llamada aterradora. –

1

Quizás, pero ¿por qué no? En ambos casos, debe de alguna manera ser explícito de qué clase es, porque la forma implícita no funcionó. Tal vez puedas establecer la celda explícitamente de alguna manera, pero no hay ninguna razón para hacer eso. Solo pasa los parámetros de forma explícita.

def __init__(self): 
    print('calling __init__') 
    super(self.__class__, self).__init__() 

class C(object): 
    __init__ = __init__ 

if __name__ == '__main__': 
    c = C() 

(Es mejor si se puede pasar en 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 puede que usted podría poner la __init__ en C directamente, por lo que asumirá puede' t.

+2

Gracias por responder, pero lo que sugiere aquí es muy incorrecto. No se puede usar 'self .__ class__' con' super', como se explicó recientemente aquí: http://stackoverflow.com/questions/4883822/shortcut-for-supertypeself-self – nikow

+0

@nikow: Bueno, si vas para subclasificarlo puedes tener problemas, sí, si esa subclase también usa super(). Entonces quiere usar el mismo '__init__' para muchas clases diferentes (porque de lo contrario no lo habría definido fuera de la clase) y no sabe qué clases, y no sabe si estas clases son subclassed? En ese caso, diría que estás resolviendo el problema incorrectamente. ¿Puedo sugerir un decorador de clases o una arquitectura de componentes con adaptadores? –

+0

@Lennart: la función adicional se define en otras partes siguiendo las ideas de AOP, y esto ha funcionado bastante bien en nuestro proyecto. En el código real, estos "aspectos" se activan/desactivan en el tiempo de ejecución, por lo que el uso de clases de decorador no es realmente una opción. Solo esperaba que se pudieran habilitar las llamadas 'super()' simplificadas, después de todo se agregaron a Python 3 por una razón. – nikow

2

puede utilizar 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 desea codificar el nombre del miembro dentro de la función.

+0

Eso funcionaría, pero no estoy seguro de si preferiría la solución explícita con la clase de escritura. Después de todo * sé * a qué clase pertenecerá el método, solo quiero beneficiarme del más conveniente Python 3 'super()'. – nikow

+2

Si está haciendo algo como esto en primer lugar, probablemente quiera asignar la función a más de una clase. –

+0

@Glenn: Sí, tienes razón. Si bien este no es mi caso de uso principal, recordé que hay casos en los que esto sería un problema. – nikow

Cuestiones relacionadas