... But interfaces are clunky and traits are lightweight, and this isn't Go, so we can't just create a class as a namespace full of @staticmethods which accept the relevant object references.
* __setattribute__ -> __getitem__, __setitem__ On Monday, October 30, 2017, Wes Turner <[email protected]> wrote: > > > On Sunday, October 29, 2017, Nick Coghlan <[email protected] > <javascript:_e(%7B%7D,'cvml','[email protected]');>> wrote: > >> On 29 October 2017 at 12:25, Brendan Barnwell <[email protected]> >> 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). >> > > This is Java-y and maybe not opcode optimizable, but maybe there's a case > for defining __setattribute__ so that square brackets denote Rust-like > traits: > > https://docs.spring.io/spring-python/1.2.x/sphinx/html/ > objects-pythonconfig.html#object-definition-inheritance > > @Object(parent="request") > def request_dev(self, req=None): > > > Observe that in the following example the child definitions must > define an optional ‘req’ argument; in runtime they will be passed its value > basing on what their parent object will return. > > It's testable, but confusing to Java programmers who aren't familiar with > why Guice forces the patterns that it does: > > https://docs.spring.io/spring-python/1.2.x/sphinx/html/ > objects-more.html#testable-code > > https://github.com/google/guice/wiki/Motivation#dependency-injection > > > Like the factory, dependency injection is just a design pattern. The > core principle is to separate behaviour from dependency resolution. In our > example, the RealBillingService is not responsible for looking up the > TransactionLog and CreditCardProcessor. Instead, they're passed in as > constructor parameters: > > When these are constructor parameters, we don't need to monkeypatch attrs > in order to write tests; which, IIUC, is also partly why you'd want > traits/mixins with the proposed special Rust-like syntax: > > https://docs.pytest.org/en/latest/monkeypatch.html > > https://docs.pytest.org/en/latest/fixture.html#modularity-using-fixtures- > from-a-fixture-function (this is too magic(), too) > > But you want dynamic mixins that have an upward reference and Rust-like > syntax (and no factories). > > >> For the car/engine example, this relates to explicitly modeling the >> relationship whereby a car can have one or more engines >> > > class MultiEngine(): > zope.interface.implements(IEngine) > > https://zopeinterface.readthedocs.io/en/latest/README.html#declaring- > implemented-interfaces > > But interfaces aren't yet justified because it's only a few lines and > those are just documentation or a too-complex adapter registry dict, anyway. > > >> (but the engine may not currently be installed), >> > > So it should default to a MockEngine which also implements(IEngine) and > raises NotImplementedError > > >> >> while an engine can be installed in at most one car at any given point >> in time. >> > > But the refcounts would be too difficult > > This: > > >> >> 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/howt >> o/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()) >> > > This could be less verbose. And less likely to raise a RuntimeError. > > >> >> 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. >> > > So there's a 'factory' which passes the ref as a constructor parameter for > such ORM instances; but they generally don't need to be dynamically > modified at runtime because traits. > > >> >> Cheers, >> Nick. >> >> -- >> Nick Coghlan | [email protected] | Brisbane, Australia >> >
_______________________________________________ Python-ideas mailing list [email protected] https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
