2

I was trying to learn the concepts of multiple-inheritance in Python. Consider a class Derv derived from two classes, Base1 and Base2. Derv inherits members from the first base class only:

class Base1:
    def __init__(self):
        self.x=10

class Base2:
    def __init__(self):
        self.y=10

class Derv (Base1, Base2):
    pass

d = Derv()
print (d.__dict__)

The result is { 'x' : 10 } and reversing the order of inheritance gives only { 'y' : 10 }.

Shouldn't the derived class inherit attributes from both the base classes?

martineau
  • 119,623
  • 25
  • 170
  • 301
Yelena
  • 423
  • 3
  • 14
  • 4
    hint: `x` and `y` are not class attributes. `__init__`, however, is. – Him Oct 23 '18 at 23:24
  • so what are x and y – Yelena Oct 23 '18 at 23:27
  • 1
    It is accurate enough to say `x` and `y` are instance attributes? – rask004 Oct 23 '18 at 23:31
  • And instance attributes are not inherited? I don't really understand, coming from a mixed C++, Java, C# background, how does this relate. Can you give an elaborate answer... – Yelena Oct 23 '18 at 23:32
  • just: inheritance is a relationship classes can have with other classes, not instances of them—although the latter's behavior can be affected by the any such connections their class has. – martineau Oct 24 '18 at 00:30
  • but instances get attributes from their classes, so when a class inherits from another, instance of derived class should have all attributes from its base class(es) – Yelena Oct 24 '18 at 00:35

4 Answers4

3

I don't fully understand why it's like this, but I can tell you how to fix it:

For some reason, Python only calls the __init__ method of one of it's parents. However, this fixes your problem:

class Base1:
     def __init__(self):
         super().__init__()
         print('b1')
         self.x=10

 class Base2:
     def __init__(self):
         super().__init__() # This line isn't needed. Still not sure why
         print('b2')
         self.y=10

 class Derv (Base1, Base2):

     def __init__(self):
         super().__init__()

 d = Derv()
 print (d.__dict__)


'b2'
'b1'
{'y': 10, 'x': 10}

Update, adding print statements actually sheds some light on the situation. For example,

class Base1:
     def __init__(self):
         print('Before Base1 call to super()')
         super().__init__()
         print('b1')
         self.x=10

 class Base2:
     def __init__(self):
         print('Before Base2 call to super()')
         super().__init__() # No remaining super classes to call
         print('b2')
         self.y=10

 class Derv (Base1, Base2):

     def __init__(self):
         super().__init__()

 d = Derv()
 print (d.__dict__)

'Before Base1 call to super()' # Just before the call to super
'Before Base2 call to super()' # Just before call to super (but there are no more super classes)
'b2' # Calls the remaining super's __init__
'b1' # Finishes Base1 __init__
{'y': 10, 'x': 10}
Stephen C
  • 1,966
  • 1
  • 16
  • 30
  • so does calling super().__init__() call init on all the base classes. What exactly does super() return? – Yelena Oct 23 '18 at 23:48
  • I think it just calls the first super's `__init__` method – Stephen C Oct 23 '18 at 23:58
  • 1
    Folks, suggest you read [Python’s super() considered super!](https://rhettinger.wordpress.com/2011/05/26/super-considered-super/) by guru Raymond Hettinger. – martineau Oct 24 '18 at 00:35
2

When a class inherits from multiple superclasses, and there are 2 or more conflicting methods, the one in the first listed class is called. Because both Base1 and Base2 define __init__, the version of __init__ in the first listed class is called, therefore not defining both of the attributes.

Pika Supports Ukraine
  • 3,612
  • 10
  • 26
  • 42
  • Great, I understand this is because of MRO. But how does the call to super().__init__ call init from both the base classes. And what does super() return! – Yelena Oct 23 '18 at 23:46
  • It's more reliable to use the syntax `SuperclassName.__init__(self, ...)`, which calls the superclass constructor for the specified superclass. – Pika Supports Ukraine Oct 23 '18 at 23:52
  • See https://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance – Pika Supports Ukraine Oct 23 '18 at 23:54
1

This is explained better in Python docs.

For most purposes, in the simplest cases, you can think of the search for attributes inherited from a parent class as depth-first, left-to-right, not searching twice in the same class where there is an overlap in the hierarchy. Thus, if an attribute is not found in DerivedClassName, it is searched for in Base1, then (recursively) in the base classes of Base1, and if it was not found there, it was searched for in Base2, and so on.

Then, as __init__ is first find in your left class, it won't look for others. As other users explained, super() should be used to let Python know how to look for other __init__ methods.

Cheche
  • 1,456
  • 10
  • 27
1

You could create objects with class attributes like this:

class Base1:
    x=10

class Base2:
    y=10

Then your Derv class would indeed inherit both properties.

class Derv (Base1, Base2):
    pass

d = Derv()
print(d.x, d.y)  # prints: 10, 10

The __init__ method is called when you create an instance of an object (ie, d = Derv()). An object can only have one version of a given method, so your Derv class only inherits the first one.

Dustin Michels
  • 2,951
  • 2
  • 19
  • 31
  • I do understand this after reading the received answers. But, now the confusion is how is super().__init__() calling init from both the base classes. – Yelena Oct 23 '18 at 23:52
  • 3
    In general, `super()` calls the `__init__` method of the parent class. In the case of multiple inheritance, it should call the `__init__` method of the first parent (listed left to right) with this method defined. See: https://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance – Dustin Michels Oct 23 '18 at 23:54