
On Tue, 29 Mar 2022 at 21:34, Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Mar 29, 2022 at 06:23:06AM +1100, Chris Angelico wrote:
If I'm reading this correctly, you're defining a "conflict" as finding two implementations of the same-named method in independent subtrees of inheritance - that is, when you say "class Pizza(Crust, Topping):", you're treating Crust and Topping as completely independent, and if they both define an add_cheese method, that's a conflict.
You've never had pizza with cheesy crust *and* cheese as a topping?
That's exactly my point: I don't see this as a conflict. I *like* my pizza to have all the cheeses!
That's not inheritance. That's composition.
I wouldn't call it composition unless it was implemented using composition instead of inheritance.
I would call it a *subset* of inheritance.
Hmm, my point is that, if it's considered a conflict, then this isn't really acting as inheritance. If it's inheritance, then each method is acting on *the pizza*, but if it's composition, then some methods are acting on the pizza's crust, and others are acting on the pizza's toppings. Ultimately, the difference between composition and inheritance is one of design, and there are no fundamentally wrong decisions, just decisions where you might not like the consequences. But this is why I keep asking for non-toy examples, as it's extremely hard to discuss design matters when all we have is "class C(A, B):" to go on.
That doesn't mean that cooperative MI doesn't have problems. Other languages forbid MI altogether because of those problems, or only allow it in a severely restricted version, or use a more primitive form of super. MI as defined by Python is perhaps the most powerful, but also the most potentially complicated, complex, convoluted and confusing.
Agreed, all forms of MI have their problems. Python, by its nature, is guaranteed to create diamond inheritance with any form of MI (in contrast, C++ can have MI where two subbranches never intersect, which has quite different consequences); of all the ways of coping with the effects of diamond inheritance, the two I've found most useful in practice are Python's super ("go to the next one, not always up the hierarchy") and a broad idea of calling every parent (effective, but needs quite different concepts of cooperation - works well for init methods though). The term "cooperative" shouldn't put people off. I've almost never seen any form of object orientation in which it isn't at least somewhat cooperative. Some are more loosely coupled than others (IBM's SOM was an amazingly good system that allowed inheritance from other people's code, but required a complex build system), but you still have to follow protocol to make sure your class plays nicely with others. Most of the time, MI hierarchies are tightly coupled, since it's extremely difficult to negotiate anything otherwise. And honestly, I'm fine with that. ChrisA