¿Cómo funciona el super () de Python con herencia múltiple?

889

Soy bastante nuevo en la programación orientada a objetos de Python y tengo problemas para entender el super() función (nuevas clases de estilo), especialmente cuando se trata de herencia múltiple.

Por ejemplo, si tienes algo como:

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

Lo que no entiendo es: ¿ Third()heredará la clase ambos métodos de constructor? En caso afirmativo, ¿cuál se ejecutará con super () y por qué?

¿Y qué pasa si quieres ejecutar el otro? Sé que tiene algo que ver con el orden de resolución del método Python ( MRO ).

Calisto
fuente
De hecho, la herencia múltiple es el único caso en el que super()es útil. No recomendaría usarlo con clases que usen herencia lineal, donde es una sobrecarga inútil.
Bachsau
99
@Bachsau es técnicamente correcto ya que es una pequeña sobrecarga, pero super () es más pitónico y permite re-factorizar y cambiar el código con el tiempo. Use super () a menos que realmente necesite un método específico de clase con nombre.
Paul Whipp
2
Otro problema super()es que obliga a todas las subclases a usarlo también, mientras que cuando no lo usa super(), todos los que lo subclasifican pueden decidir por sí mismos. Si un desarrollador que lo usa no sabe super()o no sabe que se usó, pueden surgir problemas con el mro que son muy difíciles de rastrear.
Bachsau
He encontrado prácticamente cada respuesta aquí confusa de una manera u otra. En realidad, se referiría aquí en su lugar.
Matanster

Respuestas:

709

Esto se detalla con una cantidad razonable de detalles por el propio Guido en su publicación de blog Método Resolución Orden (incluidos dos intentos anteriores).

En su ejemplo, Third()llamará First.__init__. Python busca cada atributo en los padres de la clase, ya que se enumeran de izquierda a derecha. En este caso, lo estamos buscando __init__. Entonces, si define

class Third(First, Second):
    ...

Python comenzará mirando Firsty, si Firstno tiene el atributo, veráSecond .

Esta situación se vuelve más compleja cuando la herencia comienza a cruzarse (por ejemplo, si se Firsthereda deSecond ). Lea el enlace de arriba para obtener más detalles, pero, en pocas palabras, Python intentará mantener el orden en el que cada clase aparece en la lista de herencia, comenzando con la clase secundaria.

Entonces, por ejemplo, si tuvieras:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

el MRO sería [Fourth, Second, Third, First].

Por cierto: si Python no puede encontrar un orden de resolución de método coherente, generará una excepción, en lugar de recurrir al comportamiento que podría sorprender al usuario.

Editado para agregar un ejemplo de un MRO ambiguo:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

¿Debería Thirdser MRO [First, Second]o [Second, First]? No hay expectativas obvias, y Python generará un error:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

Editar: veo a varias personas argumentando que los ejemplos anteriores carecen de super()llamadas, así que permítanme explicar: El objetivo de los ejemplos es mostrar cómo se construye el MRO. Están no destinados a imprimir "primero \ Nsecond \ tercera" o lo que sea. Puede, y debe, por supuesto, jugar con el ejemplo, agregar super()llamadas, ver qué sucede y obtener una comprensión más profunda del modelo de herencia de Python. Pero mi objetivo aquí es mantenerlo simple y mostrar cómo se construye el MRO. Y está construido como lo expliqué:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)
rbp
fuente
12
Se vuelve más interesante (y, posiblemente, más confuso) cuando comienzas a llamar a super () en Primero, Segundo y Tercero [ pastebin.com/ezTyZ5Wa ].
gatoatigrado
52
Creo que la falta de super llamadas en las primeras clases es un gran problema con esta respuesta; sin discutir cómo / por qué esa importante comprensión crítica de la pregunta se pierde.
Sam Hartman
3
Esta respuesta es simplemente incorrecta. Sin llamadas super () en los padres, no sucederá nada. La respuesta de @ lifeless es la correcta.
Cerin
8
@Cerin El objetivo de este ejemplo es mostrar cómo se construye el MRO. El ejemplo NO está destinado a imprimir "primer \ nsegundo \ tercero" o lo que sea. Y el MRO es correcto: Cuarto .__ mro__ == (<clase ' main .Fourth'>, <class ' main .Second'>, <class ' main .Third'>, <class ' main .First'>, < escriba 'objeto'>)
rbp
2
Hasta donde puedo ver, a esta respuesta le falta una de las preguntas de OP, que es "¿Y qué pasa si quieres ejecutar la otra?". Me gustaría ver la respuesta a esta pregunta. ¿Se supone que debemos nombrar explícitamente la clase base?
Ray
251

Su código, y las otras respuestas, tienen errores. Faltan las super()llamadas en las dos primeras clases que se requieren para que las subclases cooperativas funcionen.

Aquí hay una versión fija del código:

class First(object):
    def __init__(self):
        super(First, self).__init__()
        print("first")

class Second(object):
    def __init__(self):
        super(Second, self).__init__()
        print("second")

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

La super()llamada encuentra el siguiente método en el MRO en cada paso, por lo que Primero y Segundo también deben tenerlo, de lo contrario, la ejecución se detiene al final de Second.__init__().

Esto es lo que obtengo:

>>> Third()
second
first
third
sin vida
fuente
90
¿Qué hacer si estas clases necesitan diferentes parámetros para inicializarse?
Calfzhou
2
"subclases cooperativas"
Quant Metropolis
66
De esta manera, los métodos init de AMBAS clases base se ejecutarán, mientras que el ejemplo original solo llama al primer init encontrado en el MRO. Supongo que eso está implícito en el término "subclasificación cooperativa", pero una aclaración habría sido útil ('explícito es mejor que implícito', ya sabes;))
Quant Metropolis
1
Sí, si está pasando parámetros diferentes a un método que se llama a través de super, todas las implementaciones de ese método que sube el MRO hacia object () deben tener firmas compatibles. Esto se puede lograr a través de parámetros de palabras clave: acepte más parámetros de los que usa el método e ignore los adicionales. En general, se considera feo hacer esto, y para la mayoría de los casos, agregar nuevos métodos es mejor, pero init es (¿casi?) Único como un nombre de método especial pero con parámetros definidos por el usuario.
sin vida el
15
El diseño de la herencia múltiple es realmente muy malo en Python. Las clases base casi necesitan saber quién lo derivará, y cuántas otras clases base derivarán los derivados, y en qué orden ... de lo contrario super, no se ejecutará (debido a la falta de coincidencia de parámetros), o no llamará ¡Algunas de las bases (porque no escribiste superen una de las bases que rompe el enlace)!
Nawaz el
186

Quería elaborar un poco la respuesta sin vida porque cuando comencé a leer sobre cómo usar super () en una jerarquía de herencia múltiple en Python, no lo obtuve de inmediato.

Lo que debe comprender es que super(MyClass, self).__init__()proporciona el siguiente __init__ método de acuerdo con el algoritmo de Orden de resolución de métodos (MRO) utilizado en el contexto de la jerarquía de herencia completa .

Esta última parte es crucial de entender. Consideremos el ejemplo nuevamente:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

De acuerdo con este artículo sobre la Orden de resolución de métodos de Guido van Rossum, la orden de resolución __init__se calcula (antes de Python 2.3) utilizando un "recorrido de izquierda a derecha de primera profundidad":

Third --> First --> object --> Second --> object

Después de eliminar todos los duplicados, excepto el último, obtenemos:

Third --> First --> Second --> object

Entonces, sigamos lo que sucede cuando instanciamos una instancia de la Thirdclase, por ejemplo x = Third().

  1. De acuerdo con MRO Third.__init__ejecuta.
    • huellas dactilares Third(): entering
    • luego se super(Third, self).__init__()ejecuta y MRO devuelve lo First.__init__que se llama.
  2. First.__init__ ejecuta
    • huellas dactilares First(): entering
    • luego se super(First, self).__init__()ejecuta y MRO devuelve lo Second.__init__que se llama.
  3. Second.__init__ ejecuta
    • huellas dactilares Second(): entering
    • luego se super(Second, self).__init__()ejecuta y MRO devuelve lo object.__init__que se llama.
  4. object.__init__ se ejecuta (no hay declaraciones de impresión en el código allí)
  5. la ejecución vuelve a lo Second.__init__que luego imprimeSecond(): exiting
  6. la ejecución vuelve a lo First.__init__que luego imprimeFirst(): exiting
  7. la ejecución vuelve a lo Third.__init__que luego imprimeThird(): exiting

Esto detalla por qué instanciar Third () resulta en:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

El algoritmo MRO se ha mejorado desde Python 2.3 en adelante para que funcione bien en casos complejos, pero supongo que el uso del "recorrido de profundidad de izquierda a derecha" + "eliminando duplicados esperados para el último" todavía funciona en la mayoría de los casos (por favor comentar si este no es el caso). ¡Asegúrate de leer la publicación de blog de Guido!

Visionscaper
fuente
66
Todavía no entiendo por qué: Inside init de First super (First, self) .__ init __ () llama init de Second, ¡porque eso es lo que dicta el MRO!
user389955
@ user389955 El objeto creado es del tipo Third que tiene todos los métodos init. Entonces, si asume que MRO crea una lista de todas las funciones de inicio en un orden específico, con cada súper llamada, irá un paso adelante hasta llegar al final.
Sreekumar R
15
Creo que el Paso 3 necesita más explicación: si Thirdno heredó de Second, entonces super(First, self).__init__llamaría object.__init__y después de regresar, se imprimiría "primero". Pero porque Thirdhereda de ambos Firsty Second, en lugar de llamar object.__init__después de que First.__init__el MRO dicta que solo object.__init__se conserva la llamada final a , y las declaraciones de impresión en Firsty Secondno se alcanzan hasta que object.__init__regresa. Como Secondfue el último en llamar object.__init__, regresa adentro Secondantes de regresar First.
MountainDrew
1
Curiosamente, PyCharm parece saber todo esto (sus sugerencias hablan sobre qué parámetros van con qué llamadas a super. También tiene alguna noción de covarianza de entradas, por lo que reconoce List[subclass] que List[superclass]si subclasses una subclase de superclass( Listproviene del typingmódulo de PEP 483 iirc).
Reb
Buena publicación, pero extraño información con respecto a los argumentos de los constructores, es decir, ¿qué sucede si Second y First esperan argumentos distintos? El constructor de First tendrá que procesar algunos de los argumentos y pasar el resto a Second. ¿Está bien? No me parece correcto que Primero necesite saber sobre los argumentos requeridos para el Segundo.
Christian K.
58

Esto se conoce como el problema del diamante , la página tiene una entrada en Python, pero en resumen, Python llamará a los métodos de la superclase de izquierda a derecha.

monoceres
fuente
Este no es el problema del diamante. El problema del diamante involucra cuatro clases y la pregunta del OP solo involucra tres.
Ian Goodfellow
147
objectes el cuarto
GP89
28

Así es como resolví el problema de tener herencia múltiple con diferentes variables para la inicialización y tener MixIns múltiples con la misma llamada de función. Tuve que agregar explícitamente variables para pasar ** kwargs y agregar una interfaz MixIn para ser un punto final para las súper llamadas.

Aquí Ahay una clase base extensible By Cson clases MixIn que proporcionan funciones f. Ay Bambos esperan parámetro ven sus __init__y Cespera w. La función ftoma un parámetro y. Qhereda de las tres clases. MixInFes la interfaz mixin para By C.


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)
brent.payne
fuente
Creo que esto quizás debería ser una pregunta y respuesta por separado, ya que el MRO es un tema lo suficientemente amplio por sí solo sin tener que lidiar con argumentos variables a través de funciones con herencia (la herencia múltiple es un caso especial de eso).
sin vida el
8
Teóricamente sí. Prácticamente, este escenario ha surgido cada vez que me he encontrado con la herencia Diamond en Python, así que lo agregué aquí. Desde entonces, aquí es donde voy cada vez que no puedo evitar limpiamente la herencia de diamantes. Aquí hay algunos enlaces adicionales para mi futuro: rhettinger.wordpress.com/2011/05/26/super-considered-super code.activestate.com/recipes/…
brent.payne
Lo que queremos son programas con nombres de parámetros semánticamente significativos. Pero en este ejemplo, casi todos los parámetros tienen nombres anónimos, lo que hará que sea mucho más difícil para el programador original documentar el código y para que otro programador lea el código.
Arthur
Se agradecería una solicitud de extracción al repositorio de github con nombres descriptivos
brent.payne
@ brent.payne Creo que @Arthur quería decir que todo su enfoque se basa en el uso de args/ en kwargslugar de parámetros con nombre.
max
25

Entiendo que esto no responde directamente a la super()pregunta, pero siento que es lo suficientemente relevante como para compartir.

También hay una manera de llamar directamente a cada clase heredada:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

Solo tenga en cuenta que si lo hace de esta manera, tendrá que llamar a cada uno manualmente, ya que estoy seguro Firstde __init__()que no se llamará.

Seaux
fuente
55
No se llamará porque no llamó a cada clase heredada. El problema es más bien que si Firsty Secondson a la vez que hereda de otra clase y decir que es directamente entonces este (punto de partida del diamante) clase común que se llama dos veces. Super está evitando esto.
Trilarion
@Trilarion Sí, estaba seguro de que no lo haría. Sin embargo, definitivamente no lo sabía y no quería afirmarlo como si lo supiera a pesar de que era muy poco probable. Ese es un buen punto sobre objectser llamado dos veces. No pensé en eso. Solo quería aclarar que llamas directamente a las clases para padres.
Seaux
Desafortunadamente, esto se rompe si init intenta acceder a algún método privado :(
Erik Aronesty
21

En general

Suponiendo que todo desciende de object(usted está solo si no lo hace), Python calcula un orden de resolución de método (MRO) basado en el árbol de herencia de su clase. El MRO cumple 3 propiedades:

  • Los niños de una clase vienen antes que sus padres.
  • Los padres izquierdos vienen antes que los padres correctos
  • Una clase solo aparece una vez en el MRO

Si no existe tal orden, errores de Python. El funcionamiento interno de esto es una alineación C3 de la ascendencia de las clases. Lea todo sobre esto aquí: https://www.python.org/download/releases/2.3/mro/

Por lo tanto, en los dos ejemplos a continuación, es:

  1. Niño
  2. Izquierda
  3. Derecha
  4. Padre

Cuando se llama a un método, la primera aparición de ese método en la MRO es la que se llama. Se omite cualquier clase que no implemente ese método. Cualquier llamada a superese método llamará a la próxima aparición de ese método en el MRO. En consecuencia, importa tanto el orden en que coloca las clases en la herencia como el lugar donde coloca las llamadassuper en los métodos.

Con superprimero en cada método

class Parent(object):
    def __init__(self):
        super(Parent, self).__init__()
        print "parent"

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child() Salidas:

parent
right
left
child

Con el superúltimo en cada método

class Parent(object):
    def __init__(self):
        print "parent"
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child() Salidas:

child
left
right
parent
Zags
fuente
Veo que puedes acceder Leftusando super()desde Child. supongamos que quiero acceder Rightdesde adentro Child. ¿Hay una manera de acceder Rightdesde Childel uso de súper? ¿O debería llamar directamente Rightdesde adentro super?
alpha_989
44
@ alpha_989 Si desea acceder únicamente al método de una clase en particular, debe hacer referencia a esa clase directamente en lugar de usar super. Super se trata de seguir la cadena de herencia, no llegar al método de una clase específica.
Zags
1
Gracias por mencionar explícitamente 'Una clase solo aparece una vez en el MRO'. Esto resolvió mi problema. Ahora finalmente entiendo cómo funciona la herencia múltiple. ¡Alguien necesitaba mencionar las propiedades de MRO!
Tushar Vazirani
18

Acerca @ comentario calfzhou de , puede utilizar, como por lo general, **kwargs:

Ejemplo de ejecución en línea

class A(object):
  def __init__(self, a, *args, **kwargs):
    print("A", a)

class B(A):
  def __init__(self, b, *args, **kwargs):
    super(B, self).__init__(*args, **kwargs)
    print("B", b)

class A1(A):
  def __init__(self, a1, *args, **kwargs):
    super(A1, self).__init__(*args, **kwargs)
    print("A1", a1)

class B1(A1, B):
  def __init__(self, b1, *args, **kwargs):
    super(B1, self).__init__(*args, **kwargs)
    print("B1", b1)


B1(a1=6, b1=5, b="hello", a=None)

Resultado:

A None
B hello
A1 6
B1 5

También puedes usarlos posicionalmente:

B1(5, 6, b="hello", a=None)

pero tienes que recordar el MRO, es realmente confuso.

Puedo ser un poco molesto, pero me di cuenta de que la gente olvidaba cada vez que usaba *argsy **kwargscuando anulaba un método, mientras que es uno de los pocos usos realmente útiles y sanos de estas 'variables mágicas'.

Marco Sulla
fuente
Wow, eso es realmente feo. Es una pena que no puedas decir a qué superclase específica quieres llamar. Aún así, esto me da aún más incentivos para usar la composición y evitar la herencia múltiple como la peste.
Tom Busby
15

Otro punto aún no cubierto es pasar parámetros para la inicialización de clases. Desde el destino desuper depende de la subclase, la única buena manera de pasar parámetros es empacarlos todos juntos. Luego tenga cuidado de no tener el mismo nombre de parámetro con significados diferentes.

Ejemplo:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

da:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

Llamar a la superclase __init__directamente a una asignación más directa de parámetros es tentador, pero falla si hay algunasuper llamada en una superclase y / o si se cambia el MRO y se puede llamar a la clase A varias veces, dependiendo de la implementación.

Para concluir: la herencia cooperativa y los parámetros súper específicos para la inicialización no funcionan muy bien juntos.

Trilarion
fuente
5
class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

class Second(object):
  def __init__(self, a):
    print "second", a
    super(Second, self).__init__()

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__(10)
    print "that's it"

t = Third()

La salida es

first 10
second 20
that's it

Llamar a Third () localiza el init definido en Third. Y llamar a super en esa rutina invoca init definido en Primero. MRO = [Primero, Segundo]. Ahora llamar a super en init definido en Primero continuará buscando MRO y encontrará init definido en Segundo, y cualquier llamada a super golpeará el objeto predeterminado init . Espero que este ejemplo aclare el concepto.

Si no llamas super desde Primero. La cadena se detiene y obtendrá el siguiente resultado.

first 10
that's it
Seraj Ahmad
fuente
1
eso es porque en la clase Primero, primero llamaste 'imprimir' y luego 'súper'.
Qi rocoso
2
eso fue para ilustrar el orden de llamada
Seraj Ahmad
4

En learningpythonthehardway aprendo algo llamado super () una función incorporada si no se equivoca. Llamar a la función super () puede ayudar a que la herencia pase a través del padre y los 'hermanos' y ayudarlo a ver más claramente. Todavía soy un principiante, pero me encanta compartir mi experiencia al usar este super () en python2.7.

Si ha leído los comentarios en esta página, escuchará acerca de la Orden de resolución de método (MRO), siendo la función que escribió el método, MRO utilizará el esquema de Profundidad-Primero-Izquierda a Derecha para buscar y ejecutar. Puedes investigar más sobre eso.

Al agregar la función super ()

super(First, self).__init__() #example for class First.

Puede conectar varias instancias y 'familias' con super (), agregando todas y cada una de ellas. ¡Y ejecutará los métodos, los revisará y se asegurará de que no se los pierda! Sin embargo, agregarlos antes o después hace una diferencia, usted sabrá si ha realizado el ejercicio de aprendizaje 44. ¡Que empiece la diversión!

Tomando el siguiente ejemplo, puede copiar y pegar e intentar ejecutarlo:

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

class Third(First):
    def __init__(self):
        print("third (before)")
        super(Third, self).__init__()
        print("third (after)")


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

Como funciona La instancia de 5th () será así. Cada paso va de una clase a otra donde se agrega la superfunción.

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

¡Se encontró al padre e irá a Tercero y Cuarto!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

¡Ahora se ha accedido a todas las clases con super ()! Se ha encontrado y ejecutado la clase padre y ahora continúa desempaquetando la función en las herencias para finalizar los códigos.

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

El resultado del programa anterior:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

Para mí, agregar super () me permite ver más claramente cómo Python ejecutaría mi codificación y asegurarme de que la herencia pueda acceder al método que pretendía.

Se reunirá con usted
fuente
Gracias por la demostración detallada!
Tushar Vazirani
3

Me gustaría agregar a lo que @Visionscaper dice en la parte superior:

Third --> First --> object --> Second --> object

En este caso, el intérprete no filtra la clase de objeto porque está duplicada, sino porque Second aparece en una posición de cabeza y no aparece en la posición de cola en un subconjunto de jerarquía. Mientras que el objeto solo aparece en posiciones de cola y no se considera una posición fuerte en el algoritmo C3 para determinar la prioridad.

La linealización (mro) de una clase C, L (C), es el

  • la clase C
  • más la fusión de
    • linealización de sus padres P1, P2, .. = L (P1, P2, ...) y
    • la lista de sus padres P1, P2, ..

La fusión linealizada se realiza seleccionando las clases comunes que aparecen como el encabezado de las listas y no como la cola, ya que el orden importa (se aclarará a continuación)

La linealización de Third se puede calcular de la siguiente manera:

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

Por lo tanto, para una implementación super () en el siguiente código:

class First(object):
  def __init__(self):
    super(First, self).__init__()
    print "first"

class Second(object):
  def __init__(self):
    super(Second, self).__init__()
    print "second"

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__()
    print "that's it"

resulta obvio cómo se resolverá este método

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"
supi
fuente
"más bien es porque Second aparece en una posición de cabeza y no aparece en la posición de cola en un subconjunto de jerarquía". No está claro qué es una posición de cabeza o cola, ni qué es un subconjunto de jerarquía o a qué subconjunto se refiere.
OrangeSherbet
La posición de cola se refiere a clases que son más altas en la jerarquía de clases y viceversa. La clase base 'objeto' está al final de la cola. La clave para entender el algoritmo mro es cómo aparece 'Segundo' como el super de 'Primero'. Normalmente asumiremos que es la clase 'objeto'. Eso es cierto, pero solo en la perspectiva de la clase 'Primera'. Sin embargo, cuando se ve desde la perspectiva de la clase 'Tercera', el orden de jerarquía para 'Primero' es diferente y se calcula como se muestra arriba. El algoritmo mro intenta crear esta perspectiva (o subconjunto de jerarquía) para todas las clases heredadas múltiples
supi
3

En python 3.5+ la herencia se ve predecible y muy agradable para mí. Por favor mira este código:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


class Third(First, Second):
  def foo(self):
    print(" Third(): entering")
    super().foo()
    print(" Third(): exiting")


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

Salidas:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

Como puede ver, llama a foo exactamente UNA vez por cada cadena heredada en el mismo orden en que fue heredada. Puede obtener ese pedido llamando . mro :

Cuarto -> Tercero -> Primero -> Segundo -> Base -> objeto

rfedorov
fuente
2

Tal vez todavía hay algo que se puede agregar, un pequeño ejemplo con Django rest_framework y decoradores. Esto proporciona una respuesta a la pregunta implícita: "¿por qué querría esto de todos modos?"

Como se dijo: estamos con Django rest_framework, y estamos usando vistas genéricas, y para cada tipo de objetos en nuestra base de datos nos encontramos con una clase de vista que proporciona GET y POST para listas de objetos, y otra clase de vista que proporciona GET , PUT y DELETE para objetos individuales.

Ahora, POST, PUT y DELETE que queremos decorar con el login_required de Django. Observe cómo esto toca ambas clases, pero no todos los métodos en ninguna clase.

Una solución podría pasar por herencia múltiple.

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

Del mismo modo para los otros métodos.

En la lista de herencia de mis clases concretas, agregaría mi LoginToPostantes ListCreateAPIViewy LoginToPutOrDeleteantes RetrieveUpdateDestroyAPIView. Mis clases concretas getpermanecerían sin decoración.

mariotomo
fuente
1

Publicando esta respuesta para mi futura referencia.

La herencia múltiple de Python debe usar un modelo de diamante y la firma de la función no debe cambiar en el modelo.

    A
   / \
  B   C
   \ /
    D

El fragmento de código de muestra sería;

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

Aquí la clase A es object

Akhil Nadh PC
fuente
1
ATambién debería estar llamando __init__. Ano "inventó" el método __init__, por lo que no puede suponer que alguna otra clase pueda haberlo hecho Aanteriormente en su MRO. La única clase cuyo __init__método no llama (y no debería llamar) super().__init__es object.
chepner
Si. Es por eso que he escrito A, objectquizás creo que debería escribir en su class A (object) : lugar
Akhil Nadh PC
Ano puede ser objectsi está agregando un parámetro a su __init__.
chepner