¿Está super () roto en Python-2.x?

A menudo se dice que super debería evitarse en Python 2. Al usar super en Python 2, descubrí que nunca actúa de la manera que espero a menos que proporcione todos los argumentos, como el ejemplo:

 super(ThisClass, self).some_func(*args, **kwargs) 

Me parece que esto derrota el propósito de usar super() , ni es más conciso, ni mucho mejor que TheBaseClass.some_func(self, *args, **kwargs) . Para la mayoría de los propósitos, el método de resolución es un cuento de hadas distante.

  • Aparte del hecho de que 2.7 es la última versión importante de Python 2, ¿por qué la super se queda rota en Python 2?
  • ¿Cómo y por qué ha cambiado el super de Python 3 ? ¿Hay alguna advertencia?
  • ¿Cuándo y por qué debería usar super en el futuro?

super() no está roto, simplemente no debe considerarse la forma estándar de llamar a un método de la clase base. Esto no cambió con Python 3.x. Lo único que cambió es que no necesita pasar los argumentos self, cls en el caso estándar que self es el primer parámetro de la función actual y cls es la clase que se está definiendo actualmente.

Con respecto a su pregunta sobre cuándo utilizar realmente super() , mi respuesta sería: casi nunca. Personalmente trato de evitar el tipo de herencia múltiple que haría que super() útil.

Edición : un ejemplo de la vida real en el que me topé una vez: tuve algunas clases que definían un método run() , algunas de las cuales tenían clases base. Utilicé super() para llamar a los constructores heredados, no pensé que importara porque estaba usando solo una herencia:

 class A(object): def __init__(self, i): self.i = i def run(self, value): return self.i * value class B(A): def __init__(self, i, j): super(B, self).__init__(i) self.j = j def run(self, value): return super(B, self).run(value) + self.j 

Solo imagine que hubo varias de estas clases, todas con prototipos de constructor individuales, y todas con la misma interfaz para run() .

Ahora quería agregar alguna funcionalidad adicional a todas estas clases, por ejemplo, el registro. La funcionalidad adicional requería que se definiera un método adicional en todas estas clases, por ejemplo, info() . No quería invadir las clases originales, sino más bien definir un segundo conjunto de clases que heredan de las originales, agregando el método info() y heredando de una mezcla que proporciona el registro real. Ahora, ya no podía usar super() en el constructor, así que usé llamadas directas:

 class Logger(object): def __init__(self, name): self.name = name def run_logged(self, value): print "Running", self.name, "with info", self.info() return self.run(value) class BLogged(B, Logger): def __init__(self, i, j): B.__init__(self, i, j) Logger.__init__("B") def info(self): return 42 

Aquí las cosas dejan de funcionar. La llamada super() en el constructor de la clase base de repente llama a Logger.__init__() , y BLogged no puede hacer nada al respecto. En realidad, no hay manera de hacer que esto funcione, excepto para eliminar la llamada super() en B en sí.

[ Otra edición : Parece que no he expresado mi punto de vista, a juzgar por todos los comentarios aquí y debajo de las otras respuestas. Aquí es cómo hacer que este código funcione usando super() :

 class A(object): def __init__(self, i, **kwargs): super(A, self).__init__(**kwargs) self.i = i def run(self, value): return self.i * value class B(A): def __init__(self, j, **kwargs): super(B, self).__init__(**kwargs) self.j = j def run(self, value): return super(B, self).run(value) + self.j class Logger(object): def __init__(self, name, **kwargs): super(Logger,self).__init__(**kwargs) self.name = name def run_logged(self, value): print "Running", self.name, "with info", self.info() return self.run(value) class BLogged(B, Logger): def __init__(self, **kwargs): super(BLogged, self).__init__(name="B", **kwargs) def info(self): return 42 b = BLogged(i=3, j=4) 

Compare esto con el uso de llamadas de superclase explícitas. Tú decides qué versión prefieres.]

Esta y otras historias similares son la razón por la que creo que super() no debe considerarse la forma estándar de llamar a los métodos de la clase base . No significa que super() está roto.

super() no está roto, en Python 2 o Python 3.

Consideremos los argumentos de la publicación del blog:

  • No hace lo que suena como lo hace.

De acuerdo, puede estar de acuerdo o en desacuerdo con eso, es bastante subjetivo. ¿Cómo se debería haber llamado entonces? super() es un reemplazo para llamar a la superclase directamente, por lo que el nombre me parece bien. NO llama directamente a la superclase, porque si eso fuera todo lo que hacía, no tendría sentido, ya que podría hacerlo de todos modos. De acuerdo, es cierto que puede que no sea obvio, pero los casos en los que necesita super() generalmente no son obvios. Si lo necesitas, estás haciendo una herencia múltiple bastante peluda. No va a ser obvio. (O estás haciendo una mezcla simple, en cuyo caso será bastante obvio y se comportará como esperas, incluso si no leíste los documentos).

Si puede llamar a la superclase directamente, eso es probablemente lo que terminará haciendo. Esa es la manera fácil e intuitiva de hacerlo. super() solo entra en juego cuando eso no funciona.

  • No encaja bien con llamar a la superclase directamente.

Sí, porque está diseñado para resolver un problema haciendo eso. Puede llamar a la superclase directamente si, y solo si, sabe exactamente qué clase es. Lo que no hace para los mixins, por ejemplo, o cuando su jerarquía de clases está tan desordenada que en realidad está fusionando dos twigs (que es el ejemplo típico en todos los ejemplos de uso de super() ).

Entonces, siempre que cada clase en su jerarquía de clases tenga un lugar bien definido, llamar a la superclase funciona directamente. Si no lo hace, entonces no funciona, y en ese caso debe usar super() lugar. Ese es el punto de super() que determina qué es la “próxima superclase” según el MRO, sin que tenga que especificarlo explícitamente, porque no siempre puede hacerlo porque no siempre sabe qué es, por ejemplo cuando se usan mixins.

  • El lenguaje de progtwigción completamente diferente Dylan, una especie de cosa ligera, resuelve esto de otra manera que no se puede usar en Python porque es muy diferente.

Eh ¿OKAY?

  • super() no llama a tu superclase.

Sí, dijiste eso.

  • No mezcles llamadas super() y directas.

Sí, tú también dijiste eso.

Entonces, hay dos argumentos en contra: 1. El nombre es malo. 2. Tienes que usarlo consistentemente.

Eso no se traduce en que se “rompa” o que se debe “evitar”.

Pareces implicar en tu post que

 def some_func(self, *args, **kwargs): self.__class__.some_func(self, *args, **kwargs) 

No es una recursión infinita. Es, y super sería más correcto.

Además, sí, se requiere que pase todos los argumentos a super() . Esto es un poco como quejarse de que max() no funciona como se esperaba a menos que pases todos los números que deseas verificar.

Sin embargo, en 3.x, se necesitan menos argumentos: puedes hacer super().foo(*args, **kwargs) lugar de super(ThisClass, self).foo(*args, **kwargs) .


De todos modos, no estoy seguro de ninguna situación en la que se deba evitar la súper. Su comportamiento es solo “extraño” cuando el MI está involucrado, y cuando el MI está involucrado, super() es básicamente su única esperanza para una solución correcta. En Herencia única es solo un poco más verbal que SuperClass.foo(self, *args, **kwargs) , y no hace nada diferente.

Creo que estoy de acuerdo con Sven en que vale la pena evitar este tipo de IM, pero no estoy de acuerdo con que valga la pena evitar el super . Si se supone que su clase se debe heredar, los super usuarios de su clase tienen la esperanza de hacer que funcione el MI, si son raros de esa manera, hace que su clase sea más usable.

¿Leíste el artículo que lo vinculas? No concluye que se debe evitar el uso de super , pero que debe tener cuidado con sus advertencias al usarlo. Estas advertencias se resumen en el artículo, aunque no estoy de acuerdo con sus sugerencias.

El punto principal del artículo es que la herencia múltiple puede ensuciarse, y super no ayuda tanto como querría el autor. Sin embargo, hacer herencia múltiple sin super es a menudo incluso más complicado.

Si no está haciendo herencia múltiple, super le da la ventaja de que cualquier persona que herede de su clase puede agregar mixins simples y su __init__ se llamará correctamente. Solo recuerde llamar siempre al __init__ de la superclase, incluso cuando esté heredando de un object , y pasarle todos los argumentos restantes ( *a **kw ). Cuando llama a otros métodos de la clase padre, también use super , pero esta vez use la firma adecuada que ya conoce (es decir, asegúrese de que tengan la misma firma en todas las clases).

Si está haciendo una herencia múltiple, tendrá que profundizar más que eso y, probablemente, volver a leer el mismo artículo con más cuidado para estar al tanto de las advertencias. Y también es solo durante la herencia múltiple cuando es posible que una situación en la que una llamada explícita al padre sea mejor que super , pero sin un escenario específico, nadie puede decirle si se debe usar super o no.

El único cambio en super en Python 3.x es que no es necesario que pases explícitamente la clase actual y tu self de ser. Esto hace que el super sea ​​más atractivo, porque usarlo significaría que no habrá encoding de la clase principal ni de la clase actual.

@Sven Marnach:

El problema con su ejemplo es que mezcla las llamadas explícitas de superclase B.__init__ y Logger.__init__ en Logger.__init__ con super() en B. Eso no funcionará. O utiliza todas las llamadas de superclase explícitas o usa super() en todas las clases. Cuando usas super() necesitas usarlo en todas las clases involucradas, incluyendo AI think. También en su ejemplo, creo que podría usar llamadas de superclase explícitas en todas las clases, es decir, use A.__init__ en la clase B.

Cuando no hay herencia de diamante, creo que super () no tiene mucha ventaja. Sin embargo, el problema es que no sabe de antemano si obtendrá alguna herencia de diamante en el futuro, por lo que, en ese caso, sería aconsejable utilizar super() todos modos (pero luego usarlo de manera consistente). De lo contrario, tendría que cambiar todas las clases más adelante o tener problemas.