-3

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?

2
  • 1
    The highest-voted answer 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. Commented Jan 18, 2022 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. Commented Jan 19, 2022 at 8:58

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Why would One.__init__ call Two.__init__? One doesn't know about Two.
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 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.
Dylan (which I think is where super drew inspiration from) called it next-method :)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.