Pregunta ¿Cómo funciona Python's super () con herencia múltiple?


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

Por ejemplo, si tiene 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: ¿el Third() clase heredar ambos métodos 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 de métodos de Python (MRO)


610
2017-07-18 21:40


origen


Respuestas:


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

En tu 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, estamos buscando __init__. Entonces, si defines

class Third(First, Second):
    ...

Python comenzará mirando a First, y si First no tiene el atributo, luego verá Second.

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

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 una orden de resolución de método coherente, generará una excepción, en lugar de recurrir a un comportamiento que pueda sorprender al usuario.

Editado para agregar un ejemplo de 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 Third's MRO ser [First, Second] o [Second, First]? No hay una expectativa obvia, 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 super() llamadas, así que permítanme explicar: El objetivo de los ejemplos es mostrar cómo se construye el MRO. Son no destinado a imprimir "first \ nsecond \ third" o lo que sea. Puede - y debería, por supuesto, jugar con el ejemplo, agregar super() llamadas, ver qué pasa 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'>)

502
2017-07-18 21:52



Tu código y las demás respuestas son todos errores. Ellos están perdiendo el super() llama a las primeras dos clases que se requieren para que funcione la subclase cooperativa.

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

los super() llamada encuentra el siguiente método en el MRO en cada paso, por lo que primero y segundo tienen que tenerlo también; de lo contrario, la ejecución se detiene al final de Second.__init__().

Esto es lo que obtengo:

>>> Third()
second
first
third

177
2018-04-30 23:54



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

Lo que necesitas entender es que super(MyClass, self).__init__()proporciona el siguiente  __init__ método según el algoritmo de Resolución de orden de método (MRO) utilizado en el contexto de la jerarquía de herencia completa.

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

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"

De acuerdo con este artículo sobre la Orden de Resolución de Método por Guido van Rossum, la orden de resolver __init__ se calcula (antes de Python 2.3) utilizando un "cruce de profundidad de izquierda a derecha":

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 Third clase, p. x = Third().

  1. De acuerdo con MRO __init__ de Third se llama primero.

  2. Luego, según el MRO, dentro del __init__ método super(Third, self).__init__() resuelve a la __init__ método de Primero, que se llama.

  3. Dentro __init__ de Primero super(First, self).__init__() llama al __init__ de ¡Segundo, porque eso es lo que dicta el MRO!

  4. Dentro __init__ de segundo super(Second, self).__init__() llamadas el __init__ de objeto, que no es nada. Después de esto "segundo" está impreso.

  5. Después super(First, self).__init__() terminado, "primero" está impreso.

  6. Después super(Third, self).__init__() terminado, "eso es todo" está impreso.

Esto detalla por qué instanciar los resultados de Third () en:

>>> x = Third()
second
first
that's it

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


122
2018-05-12 09:51



Esto se conoce como el Problema de 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.


45
2017-07-18 21:50



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

aquí A es una clase base extensible y B y C son clases MixIn que proporcionan funciones f. A y B ambos esperan parámetro v en su __init__ y C espera w. La función f toma un parámetro y. Q hereda de las tres clases. MixInF es la interfaz mixin para B y 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)

21
2017-08-17 19:22



Entiendo que esto no responde directamente al super() pregunta, pero creo que es lo suficientemente relevante como 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 Firstes __init__() no será llamado.


15
2018-01-19 17:22



Acerca de Comentario de @ calfzhou, puedes usar, como siempre, **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 debes recordar el MRO, es realmente confuso.

Puedo ser un poco molesto, pero noté que la gente se olvidaba cada vez que usaba *args y **kwargs cuando anulan un método, mientras que es uno de los pocos usos realmente útiles y sensatos de estas 'variables mágicas'.


13
2018-04-21 21:33