Search code examples
pythonmultiple-inheritance

Grandchild inheriting from Parent class - Python


I am learning all about Python classes and I have a lot of ground to cover. I came across an example that got me a bit confused.

These are the parent classes

Class X
Class Y
Class Z

Child classes are:

Class A (X,Y)
Class B (Y,Z)

Grandchild class is:

Class M (A,B,Z)

Doesn't Class M inherit Class Z through inheriting from Class B or what would the reason be for this type of structure? Class M would just ignore the second time Class Z is inherited wouldn't it be, or am I missing something?


Solution

  • Class M would just inherit the Class Z attributes twice (redundant) wouldn't it be, or am I missing something?

    No, there are no "duplicated" attributes, Python performs a linearization they can the Method Resolution Order (MRO) as is, for instance, explained here. You are however correct that here adding Z to the list does not change anything.

    They first construct MRO's for the parents, so:

    MRO(X) = (X,object)
    MRO(Y) = (Y,object)
    MRO(Z) = (Z,object)
    
    MRO(A) = (A,X,Y,object)
    MRO(B) = (B,Y,Z,object)
    

    and then they construct an MRO for M by merging:

    MRO(M) = (M,)+merge((A,X,Y,object),(B,Y,Z,object),(Z,object))
           = (M,A,X,B,Y,Z,object)
    

    Now each time you call a method, Python will first check if the attribute is in the internal dictionary self.__dict__ of that object). If not, Python will walk throught the MRO and attempt to find an attribute with that name. From the moment it finds one, it will stop searching.

    Finally super() is a proxy-object that does the same resolution, but starts in the MRO at the stage of the class. So in this case if you have:

    class B:
    
        def foo():
            super().bar()
    

    and you construct an object m = M() and call m.foo() then - given the foo() of B is called, super().bar will first attempt to find a bar in Y, if that fails, it will look for a bar in Z and finally in object.

    Attributes are not inherited twice. If you add an attribute like:

    self.qux = 1425
    

    then it is simply added to the internal self.__dict__ dictionary of that object.

    Stating Z explicitly however can be beneficial: if the designer of B is not sure whether Z is a real requirement. In that case you know for sure that Z will still be in the MRO if B is altered.