[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