[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