IIC2233 / syllabus-2020-1

Repositorio oficial del curso IIC2233 Programación Avanzada 🎉🎊
35 stars 68 forks source link

Comportamiento inesperado en multiherencia y uso de super #531

Closed jirarrazaval closed 4 years ago

jirarrazaval commented 4 years ago

Hola, he probado de todas las formas pero no se por qué cuando hago una subclase que hereda de otras 3, en un atributo solo hereda de la ultima clase del parentesis en que defino la clase al principio, aunque especifique en el atriuto que herede especificamente de una clase.

Lo peor es que he hecho ejemplos y si me funcionan, pero en mi codigo no y no sé por qué. Es una lata porque estoy como atrapada y no sé a quien pedirle ayuda porque si me funciona el ejemplo peor en mi codigo solo hereda de la ultima clase :(

es de esta forma (y esta si funciona pero mi codigo no)

#Codigo de prueba
class A(ABC):
    @abstractmethod
    def m(self):
        print("A")
class B(A):
    def m(self):
        print("B")
        return False
class C(A):
    def m(self):
        print("C")
class D(A):
    def m(self):
        print("D")
class E(B,C,D):
    def m(self):
        C.m(self)

E().m()

Ayuda que hago

fdoflorenzano commented 4 years ago

Hola @jirarrazaval,

Primero que todo, que bueno estés probando tu código mediante pruebas más chicas. También se aprecia el esfuerzo de crearlo y escribirlo para esconder tu solución específica. 😄

Revisé tu repositorio, y encontré donde está tu situación descrita. El problema es que el ejemplo pequeño que nos muestras no sigue exactamente lo que hay en tu código, específicamente, no agregaste unas llamadas a super().m() que ocurren en las clases intermedias (en este caso, B, C y D). Si las ponemos en tu ejemplo, se vería algo así:

from abc import ABC, abstractmethod

class A(ABC):
    @abstractmethod
    def m(self):
        print("A")

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

class C(A):
    def m(self):
        super().m()
        print("C")

class D(A):
    def m(self):
        super().m()
        print("D")

class E(B,C,D):
    def m(self):
        C.m(self)

E().m()

Si ejecutas eso, te encontrarás con algo que probablemente no esperabas. Imprime:

A
D
C

¿Por qué? ¡Porque super llama a la clase siguiente en el orden de multiherencia, no la clase madre directa necesariamente! Luego, la situación que tienes es que:

Como es una instancia de E quien debe ejecutar el super(), este seguirá el orden de herencia definido, que sería: E --> B --> C --> D --> A. Luego, si E llamara a B.m(self), obtendríamos:

A
D
C
B

Lo que ocurría no era que siempre se llame a la implementación del de más a la derecha (D en este caso), si no que se llaman a varias implementaciones, pero esta siempre se llamaba y su contenido se ejecutaba antes que el resto de las otras implementaciones.

Eso, por eso ocurre lo que indicas en tu implementación. Espero se entienda, cualquier cosa comentas aquí 😄 .

Saludos.

jirarrazaval commented 4 years ago

Aaaahh buenisimo! Muchas gracias @fdoflorenzano por tu explicación!! Pero cómo se arregla?? Osea como hago para que herede solo lo que pasa en B (incluyendo el super que hay ahi en B)?? Hay alguna forma de hacerlo correcto?? Porque no se como arreglarlo para que no se implemente todo :(

jirarrazaval commented 4 years ago

Traté de cambiar los super().m() por A().m() pero me dice que: TypeError: Can't instantiate abstract class A with abstract methods m

Enverdad no sé cual es la forma de hacerloo

fdoflorenzano commented 4 years ago

Hola @jirarrazabal,

A().m() te lanza ese error porque A() crea una nueva instancia de A, que es abstracta. Lo que necesitas es llamar a un método de otra clase usando como instancia a la actual, no crear una nueva. Nota que en el ejemplo, la implementación de m en E hace eso. Llama a m de C con la instancia actual de E :)

¡Avisa si ahora pudiste!

jirarrazaval commented 4 years ago

@fdoflorenzano pucha la verdad es que no me funciona y no entiendo lo que me dices que ponga. Porque quiero que corra lo que pasa en C. Hice esto

class E(B,C,D):

    def m(self):
        C.m(self)
 E().m()

Y el output es

A
D
C

Por lo que me dijiste recién entendí que tenía que hacer esto

class E(B,C,D):
    def m(self):
        A.m(self)
E().m()

Y imprime solo

A

Yo sólo quiero que el output sea:

A
C

Pero enserio no entiendo cómo se hace :( para que sólo corra eso

fdoflorenzano commented 4 years ago

Hola @jirarrazabal,

Para el primer caso:

class E(B,C,D):

    def m(self):
        C.m(self)
 E().m()

Como m en E llama a la implementación de C, pero la implementación de C llama a super, este último redirige a la siguiente clase en el orden de multiherencia (como explicado en en mi primer comentario), entonces llama a D, el cual luego llama recién a A.

Para el segundo:

class E(B,C,D):
    def m(self):
        A.m(self)
E().m()

Ahora m en E llama a la implementación de A directamente, por eso no pasa por una clase intermedia.

Lo que te dije en mi comentario anterior era notar que A().m() no era lo que buscabas usar, si no A.m(self). No dije donde aplicarlo.

No estoy siendo explícito a propósito para que te des cuenta como ordenar las llamadas por tu cuenta :) Todo lo que necesitas ya está dicho, dale una vuelta a como funciona el primer caso que publicaste originalmente, como funciona los últimos que comentaste, y piensa como llegar a lo que necesitas.

Saludos, quedo atento.

jirarrazaval commented 4 years ago

Aaaaahh no había entendido la diferencia entre A().m() y A.m(self) Ahora me funcionooo alfiiinnnn graciass!! :smile: