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

Greg Ewing greg.ewing at canterbury.ac.nz
Sun Dec 2 17:52:07 EST 2018


Steven D'Aprano wrote:
> Perhaps more like the principle of most 
> astonishment: the object changes from sized to unsized even if you don't 
> modify its value or its type, but merely if you look at it the wrong 
> way:

Yes, but keep in mind the purpose of the whole thing is to
provide a sequence interface while not breaking old code
that expects an iterator interface. Code that was written
to work with the existing map() will not be calling len()
on it at all, because that would never have worked.

> Neither fish nor fowl with a confusing API that is not 
> quite a sequence, not quite an iterator, not quite sized, but just 
> enough of each to lead people into error.

Yes, it's a compromise in the interests of backwards
compatibility. But there are no surprises as long as you
stick to one interface or the other. Weird things happen
if you mix them up, but sane code won't be doing that.

> I can't reproduce that behaviour with the code you give above. When I 
> try it, it returns the length 3, even after the iterator has been 
> completely consumed.

It sounds like you were still using the old version with
a broken __iter__() method. This is my current complete code
together with test cases:

#-----------------------------------------------------------
from operator import itemgetter

class MapView:

     def __init__(self, func, *args):
         self.func = func
         self.args = args
         self.iterator = None

     def __len__(self):
         if self.iterator:
             raise TypeError("Mapping iterator has no len()")
         return min(map(len, self.args))

     def __getitem__(self, i):
         return self.func(*list(map(itemgetter(i), self.args)))

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

     def __next__(self):
         if not self.iterator:
             self.iterator = iter(self)
         return next(self.iterator)

if __name__ == "__main__":

     a = [1, 2, 3, 4, 5]
     b = [2, 3, 5]

     print("As a sequence:")
     m = MapView(pow, a, b)
     print(list(m))
     print(list(m))
     print(len(m))
     print(m[1])

     print()
     print("As an iterator:")
     m = MapView(pow, iter(a), iter(b))
     print(next(m))
     print(list(m))
     print(list(m))
     try:
         print(len(m))
     except Exception as e:
         print("***", e)

     print()
     print("As an iterator over sequences:")
     m = MapView(pow, a, b)
     print(next(m))
     print(next(m))
     try:
         print(len(m))
     except Exception as e:
         print("***", e)
#-----------------------------------------------------------

This is the output I get:

As a sequence:
[1, 8, 243]
[1, 8, 243]
3
8

As an iterator:
1
[8, 243]
[]
*** Mapping iterator has no len()

As an iterator over sequences:
1
8
*** Mapping iterator has no len()

-- 
Greg


More information about the Python-ideas mailing list