subclases - super clases python




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

Soy bastante nuevo en la programación orientada a objetos de Python y tengo problemas para entender la función super() (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: ¿la Third() clase heredará ambos métodos de construcción? En caso afirmativo, ¿cuál se ejecutará con super () y por qué?

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


En general

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

  • Los niños de una clase vienen antes que sus padres.
  • Padres de izquierda vienen antes que padres de derecha
  • Una clase solo aparece una vez en el MRO

Si no existe tal ordenamiento, errores de Python. El funcionamiento interno de esto es una Linerizació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 siguientes, 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 el MRO es la que se llama. Cualquier clase que no implemente ese método se omite. Cualquier llamada a super dentro de ese método llamará a la siguiente aparición de ese método en el MRO. En consecuencia, importa tanto el orden en el que coloca las clases en herencia como el lugar en el que coloca las llamadas a super en los métodos.

Con super primero en cada metodo

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 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

Acerca del comentario de @calfzhou , puedes usar, como de costumbre, **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 de forma posicional:

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

Pero hay que recordar el MRO, es realmente confuso.

Puedo ser un poco molesto, pero me di cuenta de que las personas se olvidaban cada vez de usar *args y **kwargs cuando anulan un método, mientras que es una de las pocas aplicaciones realmente útiles y sensatas de estas "variables mágicas".


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

También hay una forma 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 bastante seguro de que no se llamará a __init__() First .


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.


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 la segunda aparece en una posición de cabecera y no aparece en la posición de cola en un subconjunto de jerarquía. Mientras que el objeto solo aparece en las 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 la

  • 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 lineal se realiza seleccionando las clases comunes que aparecen como el encabezado de las listas y no 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]

Así 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"

Se vuelve 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"

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

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 súper clase __init__ directamente a una asignación más directa de parámetros es tentador, pero falla si hay alguna super llamada en una súper clase y / o se cambia el MRO y la clase A puede llamarse varias veces, dependiendo de la implementación.

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


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

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 llamada super() encuentra el siguiente método en el MRO en cada paso, por lo que First y Second 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

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

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

Si no llamas super de First. La cadena se detiene y obtendrás la siguiente salida.

first 10
that's it






multiple-inheritance