6

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?

Ernie Peters
  • 537
  • 1
  • 6
  • 18
  • Yes, you are correct that this is redundant. Where did you see this example? I suspect that it is just contrived because I doubt you would do this in any real scenario. – Code-Apprentice Mar 16 '17 at 16:02
  • @Code-Apprentice This is a very real concern, and in part is why Java did not include multiple inheritance in its object model at all. Python handles it differently. – chepner Mar 16 '17 at 16:05
  • @chepner ooo...I know a little about how C++ implements multiple inheritance and the dangers that led Java to avoid the issue. I guess I need to learn about Python's implementation. – Code-Apprentice Mar 16 '17 at 16:07
  • I edited the last question to make a bit more sense. I knew it wouldn't create duplicates but didn't understand the redundancy. it could just be a contrived example as @Code-Apprentice mentioned. Found the example on programmiz.com under the Python Class tutorials. – Ernie Peters Mar 16 '17 at 18:14

2 Answers2

8

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.

Community
  • 1
  • 1
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
2

Apart from what @Willem has mentioned, I would like to add that, you are talking about multiple inheritance problem. For python, object instantiation is a bit different compared other languages like Java. Here, object instatiation is divided into two parts :- Object creation(using __new__ method) and object initialization(using __init__ method). Moreover, it's not necessary that child class will always have parent class's attributes. Child class get parent class's attribute, only if parent class constructor is invoked from child class(explicitly).

>>> class A(object): 
      def __init__(self): 
         self.a = 23
>>> class B(A): 
      def __init__(self): 
         self.b = 33
>>> class C(A): 
      def __init__(self): 
         self.c = 44 
         super(C, self).__init__()
>>> a = A()
>>> b = B()
>>> c = C()
>>> print (a.a) 23
>>> print (b.a) Traceback (most recent call last):   File "<stdin>", line 1, in <module> AttributeError: 'B' object has no attribute 'a'
>>> print (c.a) 23

In the above code snipped, B is not invoking A's __init__ method and so, it doesn't have a as member variable, despite the fact that it's inheriting from A class. Same thing is not the case for language like Java, where there's a fixed template of attributes, that a class will have. This's how python is different from other languages.

Attributes that an object have, are stored in __dict__ member of object and it's __getattribute__ magic method in object class, which implements attribute lookup according to mro specified by willem. You can use vars() and dir() method for introspection of instance.

Mangu Singh Rajpurohit
  • 10,806
  • 4
  • 68
  • 97