[Python-Dev] PEP 246: lossless and stateless

Phillip J. Eby pje at telecommunity.com
Sun Jan 16 03:06:51 CET 2005


At 11:50 PM 1/15/05 +0100, Just van Rossum wrote:
>Phillip J. Eby wrote:
>
> > >But it _does_ perform an implicit adaptation, via PyObject_GetIter.
> >
> > First, that's not implicit.  Second, it's not adaptation, either.
> > PyObject_GetIter invokes the '__iter__' method of its target -- a
> > method that is part of the *iterable* interface.  It has to have
> > something that's *already* iterable; it can't "adapt" a non-iterable
> > into an iterable.
> >
> > Further, if calling a method of an interface that you already have in
> > order to get another object that you don't is adaptation, then what
> > *isn't* adaptation?  Is it adaptation when you call 'next()' on an
> > iterator? Are you then "adapting" the iterator to its next yielded
> > value?
>
>That's one (contrived) way of looking at it. Another is that
>
>   y = iter(x)
>
>adapts the iterable protocol to the iterator protocol. I don't (yet) see
>why a bit of state disqualifies this from being called adaptation.

Well, if you go by the GoF "Design Patterns" book, this is actually what's 
called an "Abstract Factory":

    "Abstract Factory: Provide an interface for creating ... related or 
dependent objects without specifying their concrete classes."

So, 'iter()' is an abstract factory that creates an iterator without 
needing to specify the concrete class of iterator you want.  This is a much 
closer fit for what's happening than the GoF description of "Adapter":

    "Adapter: Convert the interface of a class into another interface 
clients expect.  Adapter lets classes work together that couldn't otherwise 
because of incompatible interfaces."

IMO, it's quite "contrived" to try and squeeze iteration into this concept, 
compared to simply saying that 'iter()' is an abstract factory that creates 
"related or dependent objects".

While it has been pointed out that the GoF book is not handed down from 
heaven or anything, its terminology is certainly widely used to describe 
certain patterns of programming.  If you read their full description of the 
adapter pattern, nothing in it is about automatically getting an adapter 
based on an interface.  It's just about the idea of *using* an adapter that 
you already have, and it's strongly implied that you only use one adapter 
for a given source and destination that need adapting, not create lots of 
instances all over the place.

So really, PEP 246 'adapt()' (like 'iter()') is more about the Abstract 
Factory pattern.  It just happens in the case of PEP 246 that it's an 
Abstract Factory that *can* create adapters, but it's not restricted to 
handing out *just* adapters.  It can also be used to create views, 
iterators, and whatever else you like.  But that's precisely what makes it 
problematic for use as a type declaration mechanism, because you run the 
risk of it serving up entirely new objects that aren't just interface 
transformers.  And of course, that's why I think that you should have to 
declare that you really want to use it for type declarations, if in fact 
it's allowed at all.  Explicit use of 'adapt()', on the other hand, can 
safely create whatever objects you want.

Oh, one other thing -- distinguishing between "adapters" and merely 
"related" objects allows you to distinguish whether you should adapt the 
object or what it wraps.  A "related" object (like an iterator) is a 
separate object, so it's safe to adapt it to other things.  An actual 
*adapter* is not a separate object, it's an extension of the object it 
wraps.  So, it should not be re-adapted when adapting again; instead the 
underlying object should be adapted.

So, while I support in principle all the use cases for "adaptation" 
(so-called) that have been discussed here, I think it's important to refine 
our terminology to distinguish between GoF "adapters" and "things you might 
want to create with an abstract factory", because they have different 
requirements and support different use cases.

We have gotten a little bogged down by our comparisons of "good" and "bad" 
adapters; perhaps to move forward we should distinguish between "adapters" 
and "views", and say that an iterator is an example of a view: you may have 
more than one view on the same thing, and although a view depends on the 
thing it "views", it doesn't really "convert an interface"; it provides 
distinct functionality on a per-view basis.

Currently, PEP 246 'adapt()' is used "in the field" to create both adapters 
and views, because 1) it's convenient, and 2) it can.  :)  However, for 
type declarations, I think it's important to distinguish between the two, 
to avoid implicit creation of additional views.

A view needs to be managed within the scope that it applies to.  By that, I 
mean for example that a 'for' loop creates an iterator view and then 
manages it within the scope of the loop.  However, if you need the iterator 
to remain valid outside the 'for' loop, you may need to first call 'iter()' 
to get an explicit iterator you can hold on to.

Similarly, if you have a file that you are reading things from by calling 
routines and passing in the file, you don't want to pass each of those 
routines a filename and have them implicitly open the file; they won't be 
reading from it sequentially then.  So, again, you have to manage the view 
by opening a file or creating a StringIO or whatever.

Granted that there are some scenarios where implicit view creation will do 
exactly the right thing, introducing it also opens the opportunity for it 
to go very badly.  Today's PEP 246 implementations are as easy to use as 
'iter()', so why not use them explicitly when you need a view?



More information about the Python-Dev mailing list