¿Qué hace 'super' en Python?


564

Cuál es la diferencia entre:

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

y:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

He visto que superse usa bastante en clases con una sola herencia. Puedo ver por qué lo usarías en herencia múltiple, pero no tengo claro cuáles son las ventajas de usarlo en este tipo de situación.

Respuestas:


309

Los beneficios de super()la herencia única son mínimos: en su mayoría, no tiene que codificar el nombre de la clase base en cada método que utiliza sus métodos principales.

Sin embargo, es casi imposible usar la herencia múltiple sin super(). Esto incluye modismos comunes como mixins, interfaces, clases abstractas, etc. Esto se extiende al código que luego extiende el suyo. Si luego alguien quisiera escribir una clase que se extendiera Childy un mixin, su código no funcionaría correctamente.


66
¿Puedes dar un ejemplo de lo que quieres decir con "no funcionaría correctamente"?
Charlie Parker

319

¿Cual es la diferencia?

SomeBaseClass.__init__(self) 

significa llamar SomeBaseClass's __init__. mientras

super(Child, self).__init__()

significa llamar a un enlace __init__de la clase padre que sigue Childen la Orden de resolución de método (MRO) de la instancia.

Si la instancia es una subclase de Child, puede haber un padre diferente que viene después en el MRO.

Explicado simplemente

Cuando escribe una clase, desea que otras clases puedan usarla. super()facilita que otras clases usen la clase que estás escribiendo.

Como dice Bob Martin, una buena arquitectura le permite posponer la toma de decisiones el mayor tiempo posible.

super() puede habilitar ese tipo de arquitectura.

Cuando otra clase subclasifica la clase que escribió, también podría heredar de otras clases. Y esas clases podrían tener una __init__que viene después de esto en __init__función del orden de las clases para la resolución del método.

Sin superusted, probablemente codificaría al padre de la clase que está escribiendo (como lo hace el ejemplo). Esto significaría que no llamaría al siguiente __init__en el MRO y, por lo tanto, no podría reutilizar el código en él.

Si está escribiendo su propio código para uso personal, es posible que no le importe esta distinción. Pero si desea que otros usen su código, usar superes una cosa que permite una mayor flexibilidad para los usuarios del código.

Python 2 contra 3

Esto funciona en Python 2 y 3:

super(Child, self).__init__()

Esto solo funciona en Python 3:

super().__init__()

Funciona sin argumentos subiendo en el marco de la pila y obteniendo el primer argumento para el método (generalmente selfpara un método de instancia o clspara un método de clase, pero podrían ser otros nombres) y encontrar la clase (por ejemplo Child) en las variables libres ( se busca con el nombre __class__como una variable de cierre libre en el método).

Prefiero demostrar la forma compatible de usar super, pero si solo usas Python 3, puedes llamarlo sin argumentos.

Indirección con compatibilidad hacia adelante

¿Qué te da? Para la herencia única, los ejemplos de la pregunta son prácticamente idénticos desde el punto de vista del análisis estático. Sin embargo, el uso superle brinda una capa de indirección con compatibilidad hacia adelante.

La compatibilidad directa es muy importante para los desarrolladores experimentados. Desea que su código siga funcionando con cambios mínimos a medida que lo cambia. Cuando mira su historial de revisiones, desea ver con precisión qué cambió cuándo.

Puede comenzar con una sola herencia, pero si decide agregar otra clase base, solo tiene que cambiar la línea con las bases: si las bases cambian en una clase de la que hereda (digamos que se agrega un mixin), cambiaría Nada en esta clase. Particularmente en Python 2, obtener los argumentos supery los argumentos correctos del método puede ser difícil. Si sabe que lo está utilizando supercorrectamente con herencia única, eso hace que la depuración sea menos difícil en el futuro.

Inyección de dependencia

Otras personas pueden usar su código e inyectar a los padres en la resolución del método:

class SomeBaseClass(object):
    def __init__(self):
        print('SomeBaseClass.__init__(self) called')

class UnsuperChild(SomeBaseClass):
    def __init__(self):
        print('UnsuperChild.__init__(self) called')
        SomeBaseClass.__init__(self)

class SuperChild(SomeBaseClass):
    def __init__(self):
        print('SuperChild.__init__(self) called')
        super(SuperChild, self).__init__()

Supongamos que agrega otra clase a su objeto y desea inyectar una clase entre Foo y Bar (para probar o por algún otro motivo):

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super(InjectMe, self).__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

El uso de un super-niño no puede inyectar la dependencia porque el niño que está usando ha codificado el método que se llamará después del suyo:

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called

Sin embargo, la clase con el hijo que usa superpuede inyectar correctamente la dependencia:

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

Dirigiendo un comentario

¿Por qué en el mundo sería útil?

Python linealiza un árbol de herencia complicado a través del algoritmo de linealización C3 para crear un Orden de resolución de método (MRO).

Queremos buscar métodos en ese orden .

Para que un método definido en un padre encuentre el siguiente en ese orden sin superél, tendría que

  1. obtener el mro del tipo de instancia
  2. busca el tipo que define el método
  3. encuentra el siguiente tipo con el método
  4. enlazar ese método y llamarlo con los argumentos esperados

El UnsuperChildno debe tener acceso a InjectMe. ¿Por qué no es la conclusión "Evitar siempre usar super"? ¿Que me estoy perdiendo aqui?

El UnsuperChildqué no tienen acceso a InjectMe. Es al UnsuperInjectorque tiene acceso InjectMe, y sin embargo no puede llamar al método de esa clase desde el método del que hereda UnsuperChild.

Ambas clases secundarias tienen la intención de llamar a un método con el mismo nombre que viene después en el MRO, que podría ser otra clase que no conocía cuando se creó.

El que no tiene supercódigos rígidos es el método de su padre, por lo tanto, ha restringido el comportamiento de su método y las subclases no pueden inyectar funcionalidad en la cadena de llamadas.

El uno con super tiene mayor flexibilidad. La cadena de llamadas para los métodos puede ser interceptada e inyectada funcionalidad.

Es posible que no necesite esa funcionalidad, pero los subclases de su código sí.

Conclusión

Siempre use superpara hacer referencia a la clase padre en lugar de codificarla.

Lo que pretende es hacer referencia a la clase principal que sigue a la línea, no específicamente a la que ve heredar el elemento secundario.

No usar superpuede imponer restricciones innecesarias a los usuarios de su código.


En C, DI es como este . El código está aquí . Si agrego una implementación más de listinterfaz, digamos que doublylinkedlistla aplicación la selecciona sin problemas. Puedo hacer que mi ejemplo sea más configurable mediante la introducción config.txty la implementación de enlaces en el momento de la carga. ¿Es este el ejemplo correcto? En caso afirmativo, ¿cómo relaciono su código? Vea el primer aviso de DI en wiki. ¿Dónde se puede configurar una nueva implementación? en su código
sobreexchange

Se crea una nueva implementación a través de la herencia, por ejemplo, donde una de las clases "Inyector" hereda de la InjectMeclase. Sin embargo, los comentarios no son para discusión, por lo que le sugiero que discuta esto más a fondo en el chat o haga una nueva pregunta en el sitio principal.
Aaron Hall

¡gran respuesta! pero cuando se usa la herencia múltiple, hay complicaciones con super () y __init__funciones. especialmente si la firma de __init__varía entre clases en la jerarquía. He agregado una respuesta que se centra en este aspecto
Aviad Rozenhek,

35

Jugué un poco super()y reconocí que podemos cambiar el orden de las llamadas.

Por ejemplo, tenemos la siguiente estructura de jerarquía:

    A
   / \
  B   C
   \ /
    D

En este caso, MRO de D será (solo para Python 3):

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

Creemos una clase donde super()llame después de la ejecución del método.

In [23]: class A(object): #  or with Python 3 can define class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          print("I'm from B")
...:          super().__init__()
...:   
...: class C(A):
...:      def __init__(self):
...:          print("I'm from C")
...:          super().__init__()
...:  
...: class D(B, C):
...:      def __init__(self):
...:          print("I'm from D")
...:          super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A

    A
   / 
  B  C
    /
    D

Entonces podemos ver que el orden de resolución es el mismo que en MRO. Pero cuando llamamos super()al comienzo del método:

In [21]: class A(object):  # or class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          super().__init__()  # or super(B, self).__init_()
...:          print("I'm from B")
...:   
...: class C(A):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from C")
...:  
...: class D(B, C):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from D")
...: d = D()
...: 
I'm from A
I'm from C
I'm from B
I'm from D

Tenemos un orden diferente, se invierte un orden de la tupla MRO.

    A
   / 
  B  C
    /
    D 

Para lecturas adicionales, recomendaría las siguientes respuestas:

  1. Ejemplo de linealización C3 con super (una gran jerarquía)
  2. Cambios importantes de comportamiento entre las clases de estilo antiguas y nuevas.
  3. La historia interna sobre clases de nuevo estilo

No entiendo por qué está cambiando el orden. La primera parte entiendo que DBCA porque D es la primera clase, luego, cuando cargue el self (B, C) eventualmente imprimirá B, C y luego solo A ya que B (A), C (A) apuntaron a self para el final parte. Si sigo este entendimiento, ¿no debería ser la segunda parte como BCAD? ¿Podría explicarme un poco, por favor?
JJson

Lo malo es que no me di cuenta de que cada instancia de cada clase se ha iniciado con super () primero. Entonces, si ese es el caso, ¿no debería ser ABCD? De alguna manera entiendo cómo llegó ACBD, pero aún no podía convencer y todavía tengo un poco de confusión. Entiendo que, d = D () llamó a la Clase D (B, C) con 2 autoparámetros, ya que super () se inicia primero, luego B se llama junto con sus atributos y luego D no se imprime antes de que C sea porque Clase D (B, C) contiene 2 autoparámetros, por lo que debe ejecutar el segundo, que es Clase C (A), después de ejecutado no hay más
autoparámetros

Se basa en la definición mro .
SKhalymon

1
luego imprimirá C luego imprimirá B y finalmente imprimirá D. ¿Estoy en lo cierto?
JJson

2
Es muy fácil entender el segundo siempre que obtenga el primero. Es como una pila. empujas la impresión '' en la pila y haces super (), cuando se hace la A, comienza a imprimir cosas en esa pila, por lo que el orden es inverso.
conceder dom

35

¿No supone todo esto que la clase base es una clase de estilo nuevo?

class A:
    def __init__(self):
        print("A.__init__()")

class B(A):
    def __init__(self):
        print("B.__init__()")
        super(B, self).__init__()

No funcionará en Python 2. class Adebe ser de nuevo estilo, es decir:class A(object)


20

Al llamar super()para resolver la versión de un método de clase, método de instancia o método estático de un padre, queremos pasar la clase actual en cuyo alcance estamos como primer argumento, para indicar a qué alcance del padre estamos tratando de resolver, y como un segundo argumento el objeto de interés para indicar a qué objeto estamos tratando de aplicar ese alcance

Considere una jerarquía de clases A, By Cdonde cada clase es el padre de la que le sigue, y a, by cinstancias respectivas de cada uno.

super(B, b) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to b, as if b was an instance of A

super(C, c) 
# resolves to the scope of C's parent i.e. B
# and applies that scope to c

super(B, c) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to c

Usar supercon un método estático

por ejemplo, usar super()desde dentro del __new__()método

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return super(A, cls).__new__(cls, *a, **kw)

Explicación:

1- pesar de que es habitual para __new__()tomar como primer parámetro una referencia a la clase de llamada, se no implementado en Python como classmethod, sino más bien un métodoestático. Es decir, una referencia a una clase debe pasarse explícitamente como primer argumento cuando se llama __new__()directamente:

# if you defined this
class A(object):
    def __new__(cls):
        pass

# calling this would raise a TypeError due to the missing argument
A.__new__()

# whereas this would be fine
A.__new__(A)

2- cuando super()llamamos para llegar a la clase principal, pasamos la clase secundaria Acomo primer argumento, luego pasamos una referencia al objeto de interés, en este caso, es la referencia de clase que se pasó cuando A.__new__(cls)se llamó. En la mayoría de los casos, también es una referencia a la clase secundaria. En algunas situaciones, podría no serlo, por ejemplo, en el caso de herencias de múltiples generaciones.

super(A, cls)

3- dado que, como regla general, __new__()es un método estático, super(A, cls).__new__también devolverá un método estático y debe proporcionarse todos los argumentos explícitamente, incluida la referencia al objeto de interés, en este caso cls.

super(A, cls).__new__(cls, *a, **kw)

4- haciendo lo mismo sin super

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return object.__new__(cls, *a, **kw)

Usar supercon un método de instancia

por ejemplo, usar super()desde dentro__init__()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        super(A, self).__init__(*a, **kw)

Explicación:

1- __init__es un método de instancia, lo que significa que toma como primer argumento una referencia a una instancia. Cuando se llama directamente desde la instancia, la referencia se pasa implícitamente, es decir, no necesita especificarla:

# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...

# you create an instance
a = A()

# you call `__init__()` from that instance and it works
a.__init__()

# you can also call `__init__()` with the class and explicitly pass the instance 
A.__init__(a)

2- al llamar super()dentro __init__(), pasamos la clase secundaria como primer argumento y el objeto de interés como segundo argumento, que en general es una referencia a una instancia de la clase secundaria.

super(A, self)

3- La llamada super(A, self)devuelve un proxy que resolverá el alcance y lo aplicará selfcomo si ahora fuera una instancia de la clase principal. Llamemos a ese proxy s. Dado que __init__()es un método de instancia, la llamada s.__init__(...)pasará implícitamente una referencia de selfcomo el primer argumento al padre__init__() .

4- hacer lo mismo sin que supertengamos que pasar una referencia a una instancia explícitamente a la versión principal de __init__().

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        object.__init__(self, *a, **kw)

Usar supercon un método de clase

class A(object):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        print "A.alternate_constructor called"
        return cls(*a, **kw)

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return super(B, cls).alternate_constructor(*a, **kw)

Explicación:

1- Se puede llamar a un método de clase directamente desde la clase y toma como primer parámetro una referencia a la clase.

# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()

2- cuando se llama super()dentro de un método de clase para resolver su versión principal, queremos pasar la clase secundaria actual como primer argumento para indicar a qué alcance primario estamos tratando de resolver, y el objeto de interés como segundo argumento para indicar a qué objeto queremos aplicar ese alcance, que en general es una referencia a la clase secundaria en sí o a una de sus subclases.

super(B, cls_or_subcls)

3- La llamada se super(B, cls)resuelve al alcance Ay se aplica a ella cls. Como alternate_constructor()es un método de clase, la llamada super(B, cls).alternate_constructor(...)pasará implícitamente una referencia de clscomo primer argumento a Ala versión dealternate_constructor()

super(B, cls).alternate_constructor()

4- para hacer lo mismo sin usar super(), necesitaría obtener una referencia a la versión independiente de A.alternate_constructor()(es decir, la versión explícita de la función). Simplemente hacer esto no funcionaría:

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return A.alternate_constructor(cls, *a, **kw)

Lo anterior no funcionaría porque el A.alternate_constructor()método toma una referencia implícita Acomo su primer argumento. El clsser pasado aquí sería su segundo argumento.

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        # first we get a reference to the unbound 
        # `A.alternate_constructor` function 
        unbound_func = A.alternate_constructor.im_func
        # now we call it and pass our own `cls` as its first argument
        return unbound_func(cls, *a, **kw)

6

Muchas respuestas geniales, pero para estudiantes visuales: primero exploremos con argumentos para super, y luego sin. ejemplo de árbol de súper herencia

Imagine que hay una instancia jackcreada a partir de la clase Jack, que tiene la cadena de herencia como se muestra en verde en la imagen. Vocación:

super(Jack, jack).method(...)

utilizará el MRO (Orden de resolución de método) de jack(su árbol de herencia en un cierto orden) y comenzará a buscar desde Jack. ¿Por qué se puede proporcionar una clase para padres? Bueno, si comenzamos a buscar desde la instancia jack, encontraría el método de la instancia, el punto es encontrar el método de sus padres.

Si uno no proporciona argumentos a super, es como el primer argumento pasado es la clase de self, y el segundo argumento pasado es self. Estos se calculan automáticamente en Python3.

Sin embargo, digamos que no queremos usar Jackel método, en lugar de pasar Jack, podríamos pasar Jenpara comenzar a buscar el método desde arriba Jen.

Busca una capa a la vez (ancho no profundidad), por ejemplo, si Adamy Sueambos tienen el método requerido, Suese encontrará primero el de.

Si Cainy Sueambos tuvieran el método requerido,Cain primero se llamaría al método. Esto corresponde en código a:

Class Jen(Cain, Sue):

MRO es de izquierda a derecha.


2

Aquí hay algunas respuestas excelentes, pero no abordan cómo usarlas super()en el caso de que diferentes clases en la jerarquía tengan firmas diferentes ... especialmente en el caso de__init__

para responder esa parte y poder usarla de manera efectiva super(), sugeriría leer mi respuesta super () y cambiar la firma de los métodos cooperativos .

Aquí está la solución a este escenario:

  1. Las clases de nivel superior en su jerarquía deben heredar de una clase personalizada como SuperObject:
  2. si las clases pueden tomar argumentos diferentes, siempre pase todos los argumentos que recibió a la súper función como argumentos de palabras clave y, siempre, acepte **kwargs.
class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

ejemplo de uso:

class A(SuperObject):
    def __init__(self, **kwargs):
        print("A")
        super(A, self).__init__(**kwargs)

class B(SuperObject):
    def __init__(self, **kwargs):
        print("B")
        super(B, self).__init__(**kwargs)

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

salida:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

0
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

Esto es bastante fácil de entender.

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

Ok, qué pasa ahora si usas super(Child,self) ?

Cuando se crea una instancia de Child, su MRO (Orden de resolución de método) está en el orden de (Child, SomeBaseClass, object) en función de la herencia. (suponga que SomeBaseClass no tiene otros padres excepto el objeto predeterminado)

Al pasar Child, self, superbusca en el MRO de la selfinstancia y devuelve el objeto proxy al lado de Child, en este caso es SomeBaseClass, este objeto invoca el __init__método de SomeBaseClass. En otras palabras, si es super(SomeBaseClass,self), el objeto proxy quesuper devuelve seríaobject

Para la herencia múltiple, el MRO podría contener muchas clases, por lo que básicamente le superpermite decidir dónde desea comenzar a buscar en el MRO.


0

Considere el siguiente código:

class X():
    def __init__(self):
        print("X")

class Y(X):
    def __init__(self):
        # X.__init__(self)
        super(Y, self).__init__()
        print("Y")

class P(X):
    def __init__(self):
        super(P, self).__init__()
        print("P")

class Q(Y, P):
    def __init__(self):
        super(Q, self).__init__()
        print("Q")

Q()

Si cambia el constructor de Ya X.__init__, obtendrá:

X
Y
Q

Pero usando super(Y, self).__init__(), obtendrás:

X
P
Y
Q

Y Po Qincluso puede estar involucrado desde otro archivo que no sabe cuando escribe Xy Y. Entonces, básicamente, no sabrá a qué se super(Child, self)referirá cuando esté escribiendo class Y(X), incluso la firma de Y es tan simple comoY(X) . Es por eso que Super podría ser una mejor opción.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.