I used to do a talk about Liskov that included the joke “CLU didn’t support object inheritance. The reason for this is that Barbara Liskov was smarter than Bjarne Stroustrup.”
I used to do a talk about Liskov that included the joke “CLU didn’t support object inheritance. The reason for this is that Barbara Liskov was smarter than Bjarne Stroustrup.”
Oh the damage that language has done to a generation, but at least it is largely passed us now.
MI is not an issue in c++, and if it were the solution would be virtual inheritance.
Some older C++ frameworks give all their objects a common base class. If that inheritance isn't virtual, developers may not be able to multiply inherit objects from that framework. That's fine, one can still inherit from classes outside the framework to "mix in" or add capabilities.
I've never understood the diamond pattern fear-mongering. It's just a rarely-encountered issue to keep in mind and handle appropriately.
One example is COM (or COM-like frameworks) where every interface inherits from IUnknown. However, there is no diamond problem because COM interfaces are pure abstract base classes and the pure virtual methods in IUnknown are implemented only once in the actual concrete class.
People need to look at a playing deck. 52 cards, and you get 8×10^67 possible orders of the deck. Don’t replicate this in code.
Virtual = subclasses can override them.
So basically, any subclass can call the method, and that method may be overridden in any other subclass.
Not necessarily. If you create a diamond (or a spiderweb :) inheritence pattern, the amount of places the method can be called and overriden grows fast.
Then people noticed it was bad, and stopped.
You should read his essay: https://www.python.org/download/releases/2.2/descrintro/
I don't think that's true, because...
> So every single time you use multiple inheritance you have diamond inheritance.
Multiple inheritance is supported but not itself “highly pervasive” in Python
> It was Guido van Rossum himself that observed the prevalence of diamond inheritance
The essay you link does not support that claim. He doesn’t observe an existing prevalence, he describes new features being added simultaneously with the MRO fix that would present new use cases where diamond inheritance may be useful.
And, its true, diamond inheritance is more common in modern Python than it was with classic classes in ancient Python, but there is a huge leap between that and “highly pervasive”.
A better phrasing would be that Guido predicted the prevalence of diamond inheritance in Python and therefore found it necessary to fix the MRO.
I needed it!
For reasons.
Good reasons? No… but I had my justification.
What does this mean? There doesn't seem to be a popular alternative to C++ yet, unfortunately.
Game dev world still has tons of C++, but also plenty of C#, I guess.
Agreed that it’s not really behind us though. Even if Rust gets used for 100% of C++’s typical domains going forward (and it’s a bit more complicated than that), there’s tens? hundreds? of millions (or maybe billions?) of lines of working C++ code out there in the wild that’ll need maintained for quite a long time - likely order decades.
struct A {
name: String,
owned: B
}
struct B {
name: String,
}
you can't have a writeable reference to both A and B at the same time.
This is alien to the way C/C++ programmers think. Yes, there are ways around it,
but you spend a lot of time in Rust getting the ownership plumbing right to make this work.I think you maybe meant to say something different because here's the most obvious thing:
impl A {
fn simultaneously_writeable(&mut self) -> (&mut str, &mut str) {
(&mut self.name, &mut self.owned.name)
}
}
Now it may take you a while to figure out if you've never done Rust before, but this is trivial.Did you perhaps mean simultaneous partial field borrows where you have two separate functions that return the name fields mutably and you want to use the references returned by those functions separately simultaneously? That's hopefully going to be solved at some point, but in practice I've only seen the problem rarely so you may be overstating the true difficulty of this problem in practice.
Also, even in a more complicated example you could use RefCell to ensure that you really are grabbing the references safely at runtime while side-stepping the compile time borrow checking rules.
This is honestly such an insane take when you think about what the physical analogue would be (which again, is how OOP is sold).
The proper thing here is that, if A is the thing, then you really only have an A and your reference into B is just that, And should be represented as such, with appropriate syntactic sugar. In Haskell, you would keep around A and use a lens into B and both get passed around separately. The semantic meaning is different.
eBay, Tinder, Youtube, Robinhood, etc, etc.
Those are all real world things that are now represented in digital world and adjusted for that.
Also "world" doesn't imply "physical", but that's different matter.
And at the end of the day that was not wildly crazy, but wildly successful!
Such school of thinking enabled generations of software engineers who created all this digital world.
> Such school of thinking enabled generations of software engineers who created all this digital world.
Same could be said for imperative or functional programming for that matter.
>Wildy successful does not mean a good idea.
Sure, but if there was FP instead of OOP, then would current digital world be better, as big, safer?
Who knows?
That includes industry standards like POSIX and Khronos, CUDA, Hip and SYCL, MPI and OpenMP, that mostly acknowledge C and C++ on their definition.
https://security.googleblog.com/2025/11/rust-in-android-move...
http://mail.python.org/pipermail/python-dev/2002-October/029...
Let me give you just one example of Python's stupid implementation of inheritance.
In Python you can initialize a class with a constructor that's not even in the inheritance chain (sorry, inheritance tree because Python developers think multiple inheritance is a good idea).
class A:
def __init__(self):
self.prop = 1
class B:
def __init__(self):
self.prop = 2
class C(A):
def __init__(self):
B.__init__(self)
c = C()
print(c.prop) # 2, no problem boss
And before you say "but no one does that", no, I've see that myself. Imagine you have a class that inherits from SteelMan but calls StealMan in it's constructor and Python's like "looks good to me".I've seen horrors you people can't imagine.
* I've seen superclass constructors called multiple times.
* I've seen constructors called out of order.
* I've seen intentional skipping of constructors (with comments saying "we have to do this because blah blah blah)
* I've seen intentional skipping of your parent's constructor and instead calling your grandparent's constructor.
* And worst of all, calling constructors which aren't even in your inheritance chain.
And before you say "but that's just a dumb thing to do", that's the exact criticism of JS/C++. If you don't use any of the footguns of JS/C++, then they're flawless too.
Python developers would say "Hurr durr, did you know that if you add a object and an array in JS you get a boolean?", completely ignoring that that's a dumb thing to do, but Python developers will call superclass constructors that don't even belong to them and think nothing of it.
------------------------------
Oh, bonus point. I've see people creating a second constructor by calling `object.__new__(C)` instead of `C()` to avoid calling `C.__init__`. I didn't even know it was possible to construct an object while skipping its constructor, but dumb people know this and they use it.
Yes, instead of putting an if condition in the constructor Python developers in the wild, people who walk among us, who put their pants on one leg at a time like the rest of us, will call `object.__new__(C)` to construct a `C` object.
def init_c():
c2 = object.__new__(C)
c2.prop2 = 'three'
print(c2.__dict__, type(c2)) # {'prop2': 'three'} <class '__main__.C'>
And Python developers will look at this and say "Wow, Python is so flawless".No, you can't. Or, at least, if you can, that’s not what you’ve shown. You’ve shown calling the initializer of an unrelated class as a cross-applied method within the initializer. Initializers and constructors are different things.
> Oh, bonus point. I've see people creating a second constructor by calling `object.__new__(C)` instead of `C()` to avoid calling `C.__init__`.
Knowing that there are two constructors that exist for normal, non-native, Python classes, and that the basic constructoe Class.__new__, and that the constructor Class() itself calls Class.__new__() and then, if Class.__new__() returns an instance i of Class, also calls Class.__init__(i) before returning i, is pretty basic Python knowledge.
> I didn't even know it was possible to construct an object while skipping its constructor, but dumb people know this and they use it.
I wouldn’t use the term “dumb people” to distinguish those who—unlike you, apparently—understand the normal Python constructors and the difference between a constructor and an initializer.
I disagree that this is basic knowledge. In python a callable is an object whose type has a __call__() method. So when we see Class() its just a syntax proxy for Metaclass.__call__(Class). That's the true (first of three?) constructor, the one then calling instance = Class.__new__(cls), and soon after Class.__init__(instance), to finally return instance.
That's not basic knowledge.
I didn't know most of that, and I've performed in a nightclub in Python, maintained a CSP networking stack in Python, presented a talk at a Python conference, implemented Python extensions with both C and cffi, and edited the Weekly Python-URL!
2. Your examples / post in general seems to be "people can break semantics and get to the internals just to do anything" which I agree is bad, but python works of the principle of "we're all consenting adults" and just because you can, doesn't mean you should.
I definitely don't consent to your code, and I wouldn't allow it to be merged in main.
If you or your team members have code like this, and it's regularly getting pushed into main, I think the issue is that you don't have safeguards for design or architecture
The difference with JavaScript "hurr durr add object and array" - is that it is not an architectural thing. That is a runtime / language semantics thing. One would be right to complain about that
Exactly. One is something in plain sight in front of ones eyes, and the other one can be well hidden, not easy to spot.
t.py:11:20 - error: Argument of type "Self@C" cannot be assigned to parameter "self" of type "B" in function "__init__"
"C*" is not assignable to "B" (reportArgumentType)
1 error, 0 warnings, 0 information
ty and pyrefly give similar results. Unfortunately, mypy doesn't see a problem by default; you need to enable strict mode. def some_function(obj):
obj.prop = 2
class Foo:
def __init__(self):
some_function(self)
# or really just like
class Foo:
def __init__(self):
self.prop = 2
Which like, yeah of course that works. You can setattr on any object you please. Python's inheritance system ends up being sane in practice because it promises you nothing except method resolution and that's how it's used. Inheritance in Python is for code reuse.Your examples genuinely haven't even scratched the surface of the weird stuff you can do when you take control of Python's machinery—self is just a convention, you can remove __init__ entirely, types are made up and the points don't matter. Foo() isn't even special it's just __call__ on the classes type and you can make that do anything.
The Liskov substitute principle in that context pretty much falls out naturally. As the entire point is to substitute in types into your generic data structure.
It is true that an interface defines certain requirements of things that claim to implement it, but merely having an interface lacks the critical essence of the LSP. The LSP is not merely a banal statement that "a thing that claims to implement an interface ought to actually implement it". It is richer and more subtle than that, though perhaps from an academic perspective, still fairly basic. In the real world a lot of code technically violates it in one way or another, though.
It might be argued that CLU is no better than typical OO languages in supporting the principle, but the principle is still valid - and it was particularly relevant at the time Liskov proposed it, as inheritance was frequently being abused as just a shortcut to do composition (fortunately, things are better now, right?)
I have some data types (structs or objects), that I want to serialize, persist, and that they have some common attributes of behaviors.
In swift I can have each object to conform to Hashable, Identifiable, Codabele, etc etc... and keep repeating the same stuff over and over, or just create a base DataObject, and have the specific data object inherit it and just .
In swift you can do it by both protocols, (and extensions of them), but after a while they start looking exactly like object inheritance, and nothing like commposition.
Composition was preferred when many other languages didn't support object oriented out the gate (think Ada, Lua, etc), and tooling (IDEs) were primitive, but almost all modern languages do support it, and the tooling in insanely great.
Composition is great when you have behaviour that can be widely different, depending on runtime conditions. But, when you keep repeating yourself over and over by adopting the same protocols, perhaps you need some inheritance.
The one negative of inheretance is that when you change some behaviour of a parent class, you need to do more refactoring as there could be other classes that depend on it. But, again, with today's IDEs and tooling, that is a lot easier.
TLDR: Composition was preferred in a world where the languages didn't suport propper object inheretance out of the gate, and tooling and IDEs were still rudemmentary.
But then if you need a DataObject with an extra field, suddenly you need to re-implement serialization and deserialization. This only saves time across classes with exactly the same fields.
I'd argue that the proper tool for recursively implementing behaviours like `Eq`, `Hashable`, or `(De)Serialize` are decorator macros, e.g. Java annotations, Rust's `derive`, or Swift's attached macros.
There are valid reasons to want each one of the things described, and I really need to add type reflexivity to the set here. Looks like horizontal traits are a completely unsolved problem, because every type of program seems to favor a different implementation of it.
> The one negative of inheretance is that when you change some behaviour of a parent class, you need to do more refactoring as there could be other classes that depend on it. But, again, with today's IDEs and tooling, that is a lot easier.
It is widely known as the "unstable base class" problem.Another one is, that there are cases, where hierarchies simply don't work well. Platypus cases.
Another one is, that inheritance hides where stuff is actually implemented and it can be tedious to find out when unfamiliar with the code. It is very implicit in nature.
> TLDR: Composition was preferred in a world where the languages didn't suport propper object inheretance out of the gate, and tooling and IDEs were still rudemmentary.
I think this is rather a rewriting of history to fit your narrative.Fact is, that at least one very modern language, that is gaining in popularity, doesn't have any inheritance, and seems to do just fine without it.
Many people still go about "solving" problems by making every noun a class, which is, frankly, a ridiculous methodology of not wanting to think much. This kind of has been addressed by Casey Muratori, who formulated it approximately like this: Making 1-to-1 mappings of things/hierarchies to hierarchies of classes/objects in the code. (https://inv.nadeko.net/watch?v=wo84LFzx5nI) This kind of representing things in the code has the programmer frequently adjusting the code and adding more specializations to it.
One silly example of this is the ever popular but terrible example of making "Car" a class and then subclassing that with various types of cars and then those by brands of cars etc. New brand of car appears on the market? Need to touch the code. New type of car? Need to touch the code. Something about regulations about what every car needs to have changes? Need to touch the code. This is exactly how it shouldn't be. Instead, one should be thinking of underlying concepts and how they could be represented so that they can either already deal with changes, or can be configured from configuration files and do not depend on the programmer adding yet another class.
Composition over inheritance is actually something, that people realized after the widespread over-use of inheritance, not the other way around, and not because of language deficiencies either. The problems with inheritance are not merely previously bad IDE or editor support. The problems are, that in some cases it is bad design.