On 2017-10-29 02:28 AM, Nick Coghlan wrote:
On 29 October 2017 at 12:25, Brendan Barnwell <brenb...@brenbarn.net
<mailto:brenb...@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 | ncogh...@gmail.com <mailto:ncogh...@gmail.com> |
Brisbane, Australia
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/