[Python-Dev] PEP 246: lossless and stateless
Alex Martelli
aleax at aleax.it
Sat Jan 15 10:30:25 CET 2005
On 2005 Jan 15, at 01:02, Glyph Lefkowitz wrote:
...
> Now, we have nowhere to hide PointPen's state on SegmentPen - and why
> were we trying to in the first place? It's a horrible breach of
> encapsulation. The whole *point* of adapters is to convert between
> *different* interfaces, not merely to rename methods on the same
> interface, or to add extra methods that work on the same data. To me,
A common implementation technique, when you'd love to associate some
extra data to an object, but can't rely on the object having a __dict__
to let you do that conveniently, is to have an auxiliary dict of
bunches of extra data, keyed by object's id(). It's a bit messier, in
that you have to deal with cleanup issues when the object goes away, as
well as suffer an extra indirectness; but in many use cases it's quite
workable. I don't see doing something like
myauxdict[id(obj)] = {'foo': 'bar'}
as "terribly invasive", and therefore neither do I see
obj.myauxfoo = 'bar'
as any more invasive -- just two implementation techniques for the same
task with somewhat different tradeoffs. The task, associating extra
data with obj without changing obj type's source, won't just go away.
Incidentally, the realization of this equivalence was a key step in my
very early acceptance of Python. In the first few days, the concept
"some external code might add an attribute to obj -- encapsulation
breach!" made me wary; then CLICK, the first time I had to associate
extra data to an object and realized the alleged ``breach'' was just a
handy implementation help for the task I needed anyway, I started
feeling much better about it.
Adapter use cases exist for all three structures:
1. the adapter just needs to change method names and signatures or
combine existing methods of the object, no state additions;
2. the adapter needs to add some per-object state, which must be shared
among different adapters which may simultaneously exist on the same
object;
3. the adapter needs to add some per-adapter state, which must be
distinct among different adapters which may simultaneously exist on the
same object.
Case [1] is simplest because you don't have to wonder whether [2] or
[3] are better, which may be why it's being thought of as "best". Case
[3] may be dubious when we talk about AUTOMATIC adaptation, because in
[3] making and using two separate adapters has very different semantics
from making just one adapter and using it twice. When you build the
adapter explicitly of course you have full control and hopefully
awareness of that. For example, in Model/View, clearly you want
multiple views on the same model and each view may well need a few
presentation data of its own; if you think of it as adaptation, it's
definitely a [3]. But do we really want _automatic_ adaptation --
passing a Model to a function which expects a View, and having some
kind of default presentation data be used to make a default view on it?
That, I guess, is the dubious part.
> "different interfaces" means that the actual meaning of the operations
> is different - sometimes subtly, sometimes dramatically. There has to
> be enough information in one interface to get started on the
> implementation of another, but the fact that such information is
> necessary doesn't mean it is sufficient. It doesn't mean that there is
> enough information in the original object to provide a complete
> implementation of a different interface.
>
> If there were enough information, why not just implement all of your
> interfaces on the original class? In the case of our hypothetical
> cSegmentPen, we *already* have to modify the implementation of the
> original class to satisfy the needs of a "stateless" adapter. When
> you're modifying cSegmentPen, why not just add the methods that you
> wanted in the first place?
Reason #1: because the author of the cSegmentPen code cannot assume he
or she knows about all the interfaces to which a cSegmentPen might be
adapted a la [3]. If he or she provides a __dict__, or makes
cSegmentPen weakly referenceable, all [3]-like adaptation needs are
covered at one (heh heh) stroke.
> Here's another example: I have a business logic class which lives in an
> object database, typically used for a web application. I convert this
> into a desktop application. Now, I want to adapt IBusinessThunk to
> IGtkUIPlug. In the process of doing so, I have to create a GTK widget,
> loaded out of some sort of resource file, and put it on the screen. I
> have to register event handlers which are associated with that adapter.
OK, a typical case of model/view and thus a [3]. The issue is whether
you want adaptation to be automatic or explicit, in such cases.
> Most of the other use-cases I can think of are like the one James
> mentions, where we really are using adaptation to shuffle around some
> method names and provide simple glossing over totally isomorphic
> functionality to provide backwards (or sideways, in the case of
> almost-identical libraries provided on different platforms or
> environments) compatibility.
And what's wrong with that? Those are the "case [1]" adapters, and
they're very useful.
I guess this boils down to the issue that you don't think there are use
cases for [2], where the extra state is needed but it had better be
per-object, shared among adapters, and not per-adapter, distinct for
each adapter.
Well, one example in the model/view area comes from 3d modeling for
mechanical engineering: the model is a complex collection of solids
which only deal with geometrical properties, the views are "cameras"
rendering scenes onto windows on the screen. Each view has some modest
state of its own (camera distance, angles, screen coordinates), but
also there are some presentation data -- alien to the model itself,
which only has geometry -- which are required to be shared among views,
such as lighting information and surface texturing. One approach would
be to first wrap the bare-model into an enriched-model, once only; and
adapt only the enriched model to the views. If it's important to have
different sets of views of the same geometry with different lighting
&c, it's the only way to go; but sometimes the functional requirement
is exactly the reverse -- ensure there is never any discrepancy among
the lighting, texturing etc of the views over the same (geometrical)
model. Nothing particularly wrong, then, in having the bunch of
information that is the "enriched model" (lighting &c) be known only to
the views but directly associated with the geometry-model.
> For these reasons I would vastly prefer it if transitivity were
> declared
> as a property of the *adaptation*, not of the adapter or the registry
> or
> to be inferred from various vaguely-defined properties like
> "losslessness" or "statelessness". I am also concerned about any
> proposal which introduces transitivity-based errors at adaptation time
> rather than at registration time, because by then it is definitely too
> late to do anything about it.
Fair enough, but for Guido's suggested syntax of "def f(X:Y):..."
meaning X=adapt(X,Y) at function entry, the issue is how that
particular "default/implicit" adaptation should behave -- is it always
allowed to be transitive, never, only when Y is an interface and not a
class, or under what specific set of constraints?
Alex
More information about the Python-Dev
mailing list