Multiple inheritance and a broken super() chain

Mats Wichmann mats at wichmann.us
Mon Jul 3 14:13:37 EDT 2023


On 7/3/23 12:01, Richard Damon via Python-list wrote:
> On 7/3/23 1:38 PM, Peter Slížik via Python-list wrote:
>> Hello.
>>
>> The legacy code I'm working with uses a classic diamond inheritance. 
>> Let me
>> call the classes *Top*, *Left*, *Right*, and *Bottom*.
>> This is a trivial textbook example. The classes were written in the
>> pre-super() era, so all of them initialized their parents and Bottom
>> initialized both Left and Right in this order.
>>
>> The result was expected: *Top* was initialized twice:
>>
>> Top.__init__() Left.__init__() Top.__init__() Right.__init__()
>> Bottom.__init__()
>>
>> Now I replaced all parent init calls with *super()*. After this, Top was
>> initialized only once.
>>
>> Top.__init__() Right.__init__() Left.__init__() Bottom.__init__()
>>
>> But at this point, I freaked out. The code is complex and I don't have 
>> the
>> time to examine its inner workings. And before, everything worked 
>> correctly
>> even though Top was initialized twice. So I decided to break the 
>> superclass
>> chain and use super() only in classes inheriting from a single parent. My
>> intent was to keep the original behavior but use super() where 
>> possible to
>> make the code more readable.
>>
>> class Top:
>> def __init__(self):
>> print("Top.__init__()")
>>
>> class Left(Top):
>> def __init__(self):
>> super().__init__()
>> print("Left.__init__()")
>>
>> class Right(Top):
>> def __init__(self):
>> super().__init__()
>> print("Right.__init__()")
>>
>> class Bottom(Left, Right):
>> def __init__(self):
>> Left.__init__(self) # Here I'm calling both parents manually
>> Right.__init__(self)
>> print("Bottom.__init__()")
>>
>> b = Bottom()
>>
>>
>> The result has surprised me:
>>
>> Top.__init__() Right.__init__() Left.__init__() Top.__init__()
>> Right.__init__() Bottom.__init__()
>>
>> Now, as I see it, from the super()'s point of view, there are two
>> inheritance chains, one starting at Left and the other at Right. But
>> *Right.__init__()* is called twice. What's going on here?
>>
>> Thanks,
>> Peter
> 
> Because the MRO from Bottom is [Bottom, Left, Right, Top] so super() in 
> Left is Right. It doesn't go to Top as the MRO knows that Right should 
> go to Top, so Left needs to go to Right to init everything, and then
> Bottom messes things up by calling Right again.

And you can see this a little better in your toy example by using begin 
*and* end prints in your initializers.

Also, you might find that because of the MRO, super() in your Bottom 
class would actually give you what you want.

And if not sure, print out Bottom.__mro__





More information about the Python-list mailing list