[Python-ideas] Suggested MapView object (Re: __len__() for map())

Greg Ewing greg.ewing at canterbury.ac.nz
Tue Dec 11 18:50:41 EST 2018


Steven D'Aprano wrote:
> The iterator protocol is that iterators must:
> 
> - have a __next__ method;
> - have an __iter__ method which returns self;
> 
> and the test for an iterator is:
> 
>     obj is iter(obj)

By that test, it identifies as a sequence, as does testing it
for the presence of __len__:

 >>> m is iter(m)
False
 >>> hasattr(m, '__len__')
True

So, code that doesn't know whether it has a sequence or iterator
and tries to find out, will conclude that it has a sequence.
Presumably it will then proceed to treat it as a sequence, which
will work fine.

> py> x = MapView(str.upper, "abcdef")  # An imposter.
> py> next(x)
> 'A'
> py> next(x)
> 'B'
> py> next(iter(x))
> 'A'

That's a valid point, but it can be fixed:

     def __iter__(self):
         return self.iterator or map(self.func, *self.args)

Now it gives

 >>> next(x)
'A'
 >>> list(x)
[]

There is still one case that will behave differently from the
current map(), i.e. using list() first and then expecting it
to behave like an exhausted iterator. I'm finding it hard to
imagine real code that would depend on that behaviour, though.

 > whether operations succeed or not depend on the
> order that you call them:
> 
> py> x = MapView(str.upper, "abcdef")
> py> len(x)*next(x)  # Safe. But only ONCE.

But what sane code is going to do that? Remember, the iterator
interface is only there for backwards compatibility. That would
fail under both Python 2 and the current Python 3.

> py> def innocent_looking_function(obj):
> ...     next(obj)
> ...
> py> x = MapView(str.upper, "abcdef")
> py> len(x)
> 6
> py> innocent_looking_function(x)
> py> len(x)
> TypeError: Mapping iterator has no len()

If you're using len(), you clearly expect to have a sequence,
not an iterator, so why are you calling a function that blindly
expects an iterator? Again, this cannot be and could never have
been working code.

> I presume this is just an oversight, but indexing continues to work even 
> when len() has been broken.

That could be fixed.

> This MapView class offers a hybrid "sequence plus iterator, together at 
> last!" double-headed API, and even its creator says that sane code 
> shouldn't use that API.

No. I would document it like this: It provides a sequence API.
It also, *for backwards compatibility*, implements some parts
of the iterator API, but new code should not rely on that,
nor should any code expect to be able to use both interfaces
on the same object.

The backwards compatibility would not be perfect, but I think
it would work in the vast majority of cases.

I also envisage that the backwards compatibility provisions
would not be kept forever, and that it would eventually become
a pure sequence object.

I'm not necessarily saying this *should* be done, just pointing
out that it's a possible strategy for migrating map() from
an iterator to a view, if we want to do that.

-- 
Greg


More information about the Python-ideas mailing list