[Python-ideas] Composition over Inheritance

Steven D'Aprano steve at pearwood.info
Sat Oct 28 12:51:37 EDT 2017


On Sat, Oct 28, 2017 at 10:19:09AM -0200, Soni L. wrote:

> >class Car:
> >     def __init__(self):
> >         self.engine = Engine()
> >         self.accelerator = AcceleratorPedal()
> >         ...
> >
> >     def start(self):
> >         # Delegate to the ignition component.
> >         self.ignition.start()
> >
> >
> >etc. Obviously this is just a very loose sketch, don't take it too
> >literally. Is this the sort of thing you are talking about?
> 
> So how do you call car.ignition.start() from car.key.turn()?

You don't -- the key is not a component of the car, its an argument of 
ignition.start. If the key doesn't fit, ignition.start() raises an 
exception and the car doesn't start.

I'm not really interested in getting into a tedious debate over the best 
way to design a Car object. As I said, the above is just a loose sketch 
illustrating composition, not a carefully planned and debugged object. 
The aim here is to understand your proposal: what exactly do you mean 
for Python to support composition over inheritence, and how does it 
differ from Python's existing support for composition?

You ignored my question: Is that the sort of thing you mean by 
composition? If not, then what do you mean by it? This is not a 
rhetorical question: I'm having difficulty understanding your proposal. 
It is too vague, and you are using terminology in ways I don't 
understand.

Maybe that's my ignorance, or maybe you're using non-standard 
terminology. Either way, if I'm having trouble, probably others are too. 
Help us understand your proposal.


> >>I am not sure how you'd set components, or test for components,
> >If you don't know how to set components, or test for them, what do you
> >know how to do with components?
> >
> >And how are components different from attributes?
> 
> They're more like conflict-free interfaces, and in this specific case 
> they're designed with duck typing in mind. (You can dynamically add and 
> remove components, and use whatever you want as the component. You 
> cannot do that with inheritance.)

What do you mean by "conflict-free interfaces"?

I can only repeat my earlier request:

> >If might help if you give a concrete example, with meaningful names. It
> >would help even better if you can contrast the way we do composition now
> >with the way you think we should do it.


> >I'm afraid that at the moment I'm parsing your post as:
> >
> >"composition is cool, we should use it; and o.[c].m() is cool syntax, we
> >should use it for composition; I'll leave the details to others".
> 
> Again, how do you call car.ignition.start() from car.key.turn()?

Maybe you can't. Maybe this is a crippling example of why composition 
isn't as good as inheritence and the OOP community is right that 
inheritence is the best thing since sliced bread. Maybe my design of the 
Car object sucks.

But who cares? None of this comes any closer to explaining your 
proposal.


> >>Thus I think o.[c].m() should be syntax sugar for o[c].m(o), with o
> >>being evaluated only once,
> >I don't see why you're using __getitem__ instead of attribute access;
> >nor do I understand why m gets o as argument instead of c.
> >
> >Wait... is this something to do with Lieberman-style delegation?
> >
> >http://web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.html
> >
> >http://code.activestate.com/recipes/519639-true-lieberman-style-delegation-in-python/
> >
> 
> TL;DR. But no, it's not some form of delegation.

One of us is using non-standard terminology, and I don't think it is me. 
(Happy to be corrected if I'm wrong.)

I understand that composition and delegation go hand in hand: you can't 
have one without the other. Composition refers to the arrangement of an 
object that is composed of other objects. Delegation refers to the way 
that the compound object calls methods on the component objects.

The point (as I understand it) of composition is that a Car doesn't just 
have an Engine, it delegates functionality to the Engine: the Car object 
derives functionality by calling Engine methods directly, rather than 
inheriting them. Car.forward() delegates to Engine.forward().

The point is that the implementation of Car.forward is found in the 
self.engine object, rather than being inherited from an Engine class.

Without delegation, the components aren't components at all, merely data 
attributes: Car.colour = 'red'.

Does this match your proposal? If not, how is your proposal different?


> It still gets `self` (which is whatever is in o[c] - which may be c 
> itself, or an arbitrary object that fulfills the contract defined by c), 
> but also gets `o` in addition to `self`. (Unless it's a plain function, 
> in which case it gets no `self`.)

That sounds like a hybrid of Lieberman-style delegation and the more 
common form. At first glance, that seems to add complexity without 
giving the advantages of either form of delegation.


> >>as that solves a lot of current issues
> >>relating to inheritance while introducing very few issues relating to
> >>python's "everything is separate" (e.g. __get__ vs __getattr__)
> >>policy.This also makes setting components and testing for components
> >>fairly trivial, and completely avoids the issues mentioned above by
> >>making their syntax illegal.
> >Above you said that you don't know how to set and test for components,
> >now you say that doing so is trivial. Which is it?
> 
> If you pay closer attention, you'll notice the two different paragraphs 
> talk about two different syntaxes.

I don't care about syntax yet. I'm still trying to understand the 
semantics of your proposal. Whether you spell this thing

    instance.[component]

    get_component(instance, 'component')

    instance!component

is less important than understand what it *does*.


> - o.[c] as a standalone syntax element, allowing things like 
> x=o.[c1].[c2]; and x=o.[c1][c2];.
> - o.[c].m() as a standalone syntax element, *disallowing* the above.

That makes no sense to me. I cannot make head or tail of what that is 
supposed to mean.


> >>(Disclaimer: This was inspired by my own programming language,
> >>Cratera[1], so I'm a bit biased here. Cratera was, in turn, inspired by
> >>Rust[2] traits.
> >Traits are normally considered to be a more restricted, safer form of
> >multiple inheritence, similar to mixins but even more restrictive.
> 
> What do you mean more restricted? 

I mean that if you have two traits with the same method:

class SpamTrait:
    def foo(self): ...

class EggTrait:
    def foo(self): ...


then you cannot use them both in a single class:

class MyClass(SpamTrait, EggTrait):
    ...

since the foo method clashes, unless MyClass explicitly specifies which 
foo method to use. Mixins and regular multiple inheritence do not have 
that restriction.

If you expect both foo methods to be called, that's just regular 
multiple inheritence, with all its complexity and disadvantages.
(See Michele Simionato numerous posts on Artima about super, multiple 
inheritence, mixins and his own traits implementation.)

The point of traits is to prevent the existence of such conflicts: either by 
prohibiting the use of both SpamTrait and EggTrait at the same time, or 
by forcing MyClass to explicitly choose which foo method gets used. 
That's safer than unrestricted mixins and multiple inheritence, since it 
reduces the complexity of the inheritence heirarchy.


> They let you have the same method in multiple components/traits and 
> not have them conflict, among other things.

I think we are in agreement here.

But in any case... traits are a form of inheritence, not composition. 
You said this proposal is inspired by Rust traits. Can you explain the 
connection between inheritence of traits and composition?



-- 
Steve


More information about the Python-ideas mailing list