[Python-ideas] Composition over Inheritance
Soni L.
fakedme+py at gmail.com
Sun Oct 29 07:44:53 EDT 2017
On 2017-10-29 02:28 AM, Nick Coghlan wrote:
> On 29 October 2017 at 12:25, Brendan Barnwell <brenbarn at brenbarn.net
> <mailto:brenbarn at brenbarn.net>> wrote:
>
> On 2017-10-28 19:13, Soni L. wrote:
>
> And to have all cars have engines, you'd do:
>
> class Car:
> def __init__(self, ???):
> self[Engine] = GasEngine()
>
> car = Car()
> car[Engine].kickstart() # kickstart gets the car as second
> argument.
>
> And if you can't do that, then you can't yet do what I'm
> proposing, and
> thus the proposal makes sense, even if it still needs some
> refining...
>
>
> As near as I can tell you can indeed do that, although
> it's still not clear to me why you'd want to. You can give Car a
> __getitem__ that on-the-fly generates an Engine object that knows
> which Car it is attached to, and then you can make
> Engine.kickstart a descriptor that knows which Engine it is
> attached to, and from that can figure out which Car it is attached to.
>
>
> Right, I think a few different things are getting confused here
> related to how different folks use composition.
>
> For most data modeling use cases, the composition model you want is
> either a tree or an acyclic graph, where the subcomponents don't know
> anything about the whole that they're a part of. This gives you good
> component isolation, and avoids circular dependencies.
>
> However, for other cases, you *do* want the child object to be aware
> of the parent - XML etrees are a classic example of this, where we
> want to allow navigation back up the tree, so each node gains a
> reference to its parent node. This often takes the form of a
> combination of delegation (parent->child references) and dependency
> inversion (child->parent reference).
>
> For the car/engine example, this relates to explicitly modeling the
> relationship whereby a car can have one or more engines (but the
> engine may not currently be installed), while an engine can be
> installed in at most one car at any given point in time.
>
> You don't even need the descriptor protocol for that though, you just
> need the subcomponent to accept the parent reference as a constructor
> parameter:
>
> class Car:
> def __init__(self, engine_type):
> self.engine = engine_type(self)
>
> However, this form of explicit dependency inversion wouldn't work as
> well if you want to be able to explicitly create an "uninstalled
> engine" instance, and then pass the engine in as a parameter to the
> class constructor:
>
> class Car:
> def __init__(self, engine):
> self.engine = engine # How would we ensure the engine is
> marked as installed here?
>
> As it turns out, Python doesn't need new syntax for this either, as
> it's all already baked into the regular attribute access syntax,
> whereby descriptor methods get passed a reference not only to the
> descriptor, but *also* to the object being accessed:
> https://docs.python.org/3/howto/descriptor.html#descriptor-protocol
>
> And then the property builtin lets you ignore the existence of the
> descriptor object entirely, and only care about the original object,
> allowing the above example to be written as:
>
> class Car:
> def __init__(self, engine):
> self.engine = engine # This implicitly marks the engine as
> installed
>
> @property
> def engine(self):
> return self._engine
>
> @engine.setter
> def engine(self, engine):
> if engine is not None:
> if self._engine is not None:
> raise RuntimeError("Car already has an engine installed")
> if engine._car is not None:
> raise RuntimeError("Engine is already installed in
> another car")
> engine._car = self
> self._engine = engine
>
> car = Car(GasEngine())
>
> ORMs use this kind of descriptor based composition management
> extensively in order to reliably model database foreign key
> relationships in a way that's mostly transparent to users of the ORM
> classes.
And this is how you miss the whole point of being able to dynamically
add/remove arbitrary components on objects you didn't create, at runtime.
Someone gave me this code and told me it explains what I'm trying to do:
https://repl.it/NYCF/3
class T:
pass
class C:
pass
c = C()
#c.[T] = 1
c.__dict__[T] = 1
I'd also like to add:
def someone_elses_lib_function(arbitrary_object):
#arbitrary_object.[T] = object()
arbitrary_object.__dict__[T] = object()
and
def another_ones_lib_function(arbitrary_object):
#if arbitrary_object.[T]:
if arbitrary_object.__dict__[T]:
#arbitrary_object.[T].thing()
arbitrary_object.__dict__[T].thing(arbitrary_object)
>
> Cheers,
> Nick.
>
> --
> Nick Coghlan | ncoghlan at gmail.com <mailto:ncoghlan at gmail.com> |
> Brisbane, Australia
>
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20171029/8657cc95/attachment.html>
More information about the Python-ideas
mailing list