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
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.
Now, as I see it, from the super()'s point of view, there are twoNo: each class has just a single inheritance chain, built up when the
inheritance chains, one starting at Left and the other at Right. But *Right.__init__()* is called twice.
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__()
Also, you might find that because of the MRO, super() in your Bottom
class would actually give you what you want.
I knew this, but I wanted to save myself some refactoring, as the legacy
code used different signatures for Left.__init__() and Right.__init__().
Also, you might find that because of the MRO, super() in your Bottom
class would actually give you what you want.
On Tue, 4 Jul 2023 at 03:39, Peter Slížik via Python-list
The legacy code I'm working with uses a classic diamond inheritance.
What happens when Top is initialized twice? This seems like a problem
waiting to happen, and when you moved to using super(), you more than
likely simplified things and fixed things.
On 03/07/2023 19:39, Chris Angelico via Python-list wrote:
On Tue, 4 Jul 2023 at 03:39, Peter Slížik via Python-list
The legacy code I'm working with uses a classic diamond inheritance.
What happens when Top is initialized twice? This seems like a problem waiting to happen, and when you moved to using super(), you more than likely simplified things and fixed things.
Slightly off topic but I wonder how many real world problems
people have experienced having the top of a diamond initialized
twice? The reason I ask is that I ran a maintenance team for
about 5 years (early 1990s) working on various OOP projects using MI;
in Lisp Flavors, C++(*) and a homebrew variant of C that supported MI.
In that time I don't recall ever having problems with top objects
being initialized twice (apart from redundant execution of code
of course).
In most cases the top object was so abstract that its init()/constructor
was only doing generic type stuff or opening database sessions/networks
etc which got lost and tidied up by garbage collectors.
So I'm curious about how big this "big problem with MI" is in
practice. I'm sure there are scenarios where it has bitten folks
but it never (or very rarely) occurred in our projects. (Of
course, being maintenance programmers, the problems may have
been ironed out before the code ever reached us! But that
wasn't usually the case...)
(*) C++ is the odd one out because it doesn't have GC, but then
neither does it have an Object superclass so very often MI in C++
does not involve creating diamonds! And especially if the MI
style is mixin based.
On 5/07/23 10:33 am, Alan Gauld wrote:
(*) C++ is the odd one out because it doesn't have GC, but then
neither does it have an Object superclass so very often MI in C++
does not involve creating diamonds! And especially if the MI
style is mixin based.
Even if all your mixins have empty constructors, in C++ there
is still a diamond problem if they have any data members, because
you end up with multiple copies of them.
But C++ has the concept of virtual base classes, which avoids the
diamond problem, albeit at the expense of making you explicitly
call all the base class constructors in your ancestry.
So I'm curious about how big this "big problem with MI" is in
Who said it's a big problem with MI?
| Sysop: | Keyop |
|---|---|
| Location: | Huddersfield, West Yorkshire, UK |
| Users: | 715 |
| Nodes: | 16 (2 / 14) |
| Uptime: | 14:23:29 |
| Calls: | 12,101 |
| Calls today: | 1 |
| Files: | 15,004 |
| Messages: | 6,518,022 |