-2

I've been trying to understand how to make sure all super().__init__()s run when writing a class that inherits from two unrelated classes. Based on answers such as this and others like it, calling super(DerivedClass, self).__init__() should do the trick ("newer style").

So I've set up a dummy example:

class One():
    def __init__(self):
        print("One runs")
        self.one = 1
        
class Two():
    def __init__(self):
        print("Two runs")
        self.two = 2
        
        
class Three(One, Two):
    def __init__(self):
        super(Three, self).__init__()
        print("Three runs")

three = Three()
print(three.two)

To my biggest surprise, this results in an error when running on Python 3.9.7. The exact output I get is:

One runs
Three runs
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/var/folders/xd/f8xs148x2j18h636phmf27zh0000gq/T/ipykernel_7384/3344160392.py in <module>
     16 
     17 three = Three()
---> 18 print(three.two)

AttributeError: 'Three' object has no attribute 'two'

This makes me super confused. Is this not the right way to do it with the "newer style"? What am I missing?

Samuel Liew
  • 76,741
  • 107
  • 159
  • 260
lte__
  • 7,175
  • 25
  • 74
  • 131
  • 1
    The [highest-voted answer](https://stackoverflow.com/a/50465583/5349916) shows that you have to make *two* calls to ``super(...).__init__`` – one for each base. See the first case "1. The base classes are unrelated, standalone classes." Other answers are showing a similar pattern for this case. – MisterMiyagi Jan 18 '22 at 18:19
  • 1
    Take note that this question linked to another *question*, not an answer. Either way, as mentioned there are many answers on that question that show *that* and *why* two super calls are required, i.e. that "calling ``super(DerivedClass, self).__init__()`` should do the trick" is not the case and what to do instead. If you can [edit] this question to clarify in how far the information in the other one is not sufficient, it will go to the reopen process. – MisterMiyagi Jan 19 '22 at 08:58

1 Answers1

2

Every __init__ is not run, because you didn't use super properly. When designing a class hierarchy that uses super, it's important that all the classes use it.

class One:
    def __init__(self):
        super().__init__()
        print("One runs")
        self.one = 1
        
class Two:
    def __init__(self):
        super().__init__()
        print("Two runs")
        self.two = 2
        
        
class Three(One, Two):
    def __init__(self):
        super().__init__()
        print("Three runs")

While the inheritance graph is a directed acyclic graph, the method resolution order is a linear ordering of the nodes in that tree. super uses that list, not the graph itself, when determining which method gets called via super().

>>> Three.mro()
[<class '__main__.Three'>, <class '__main__.One'>, <class '__main__.Two'>, <class 'object'>]

Three() calls Three.__init__, which calls One.__init__, which calls Two.__init__, which calls object.__init__, which calls nothing because object.__init__ is the top of the chain for __init__.

More detail can be found in Python's super() considered super!, which also provides advice on how to adapt One and Two when they don't already use super.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Why would `One.__init__` call `Two.__init__`? `One` doesn't know about `Two`. – Tim Roberts Jan 18 '22 at 18:19
  • 3
    It doesn't need to; `super()` use the method resolution order of the object bound to `self`, which *does* know about all the classes involved. `super` is misnamed, and does not just refer to the static parent (or one of the static parents) of the class being defined. – chepner Jan 18 '22 at 18:21
  • @chepner yep, exactly. `next` might have been a better name (obviously alreaady taken), or maybe `next_class`, because what it does it get you *the next class in the method resolution order*. – juanpa.arrivillaga Jan 18 '22 at 18:35
  • 1
    Dylan (which I think is where `super` drew inspiration from) called it `next-method` :) – chepner Jan 18 '22 at 19:45