[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