You have a valuable problem on your hands.
That question is what we call diamond problem and occurs in multiple inheritance when a class inherits from two classes that inherit from the same class.
And the problem is just this: should the attributes of A be passed to D through B or C? Of both, they would not be duplicated. In your case, you own the field atributo1
in W
. What should be the final value of atributo1
, what I go through in class X
or by class Y
?
The problem gets even worse because you misused the function super
. This may be because you don’t understand what she does.
In your case, the class Z
does not have an initializing method (__init__
) and MRO defines that the method name will be solved first in X
. Remember that we are working with an instance of Z
. Thus, the method X.__init__
will be executed first.
The method X.__init__
calls the function super
, that returns us an object proxy for the next class in the MRO-defined sequence of our instance. How we work as an instance of Z
and we’re in the class X
, the next class in the sequence defined by the MRO shall be Y
. I mean, when you do super().__init__(atributo1)
in X
, you will call the initializer Y
. WTF?
Do you understand the confusion? A class X
inheriting only from W
is invoking a method in Y
. But calm down, it’s not the seven-headed animal (but it’s the one with multiple heads :D).
If you read the job documentation super
, which I believe you have read, you will see that it works well with multiple inheritance when all classes have the same signature. But how to own the same signature if each class demands different parameters? Here you, as the developer and author of the project, should define what should be done or not. Do so by analyzing the consequences. One way is to use the wildcard parameter **kwargs
and use only named parameters. Something like:
class W:
def __init__(self, **kwargs):
self.atributo1 = kwargs['atributo1']
def metodo1(self):
print("Metodo1")
class X(W):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.atributo2 = kwargs['atributo2']
class Y(W):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.atributo3 = kwargs['atributo3']
Thus, all classes will have the same signature on their initializers and you can do:
z = Z(atributo1=1, atributo2=2, atributo3=3)
print('Atributo 1:', z.atributo1)
print('Atributo 2:', z.atributo2)
print('Atributo 3:', z.atributo3)
Getting:
Atributo 1: 1
Atributo 2: 2
Atributo 3: 3
Thanks for answering. But it may help me to better understand why I misused the function
super
? What would be the right way to implement?– L. WD
@L.WD I explained this in the reply. The
super
searches the next MRO class based on the current instance, not the class. So in class X, instead of calling the method W, you’ll call it Y - which has nothing to do with X. Read more about the diamond and MRO problem.– Woss
Thanks again. I studied a little more about MRO and using
print(Z.mro())
I was able to "visualize" the sequence better defined and in which class the search would happen... very good. But my initial doubt persists: what would then be the constructor of the Z class? It would look like this:class Z(X, Y): def __init__(self, **kwargs): super().__init__(**kwargs)
– L. WD
If you do the initializers as I put it last, you wouldn’t even need a method
__init__
inZ
. But if you need to, just follow the same idea of the other classes.– Woss
I did as I wrote in the previous comment and it worked perfectly... From what I understood then, if it were not the case of a "diamond problem" and, in my example, there was no class W (and consequently classes X and Y did not inherit anything from anyone) the constructor of class Z could be:
def __init__(self, atributo1, atributo2): super().__init__(atributo1) super(X, self).__init__(atributo2)
– L. WD
Since using the
super(X, self).__init__
I can "direct" where to look for the next builder... correct?– L. WD