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

Steven D'Aprano steve at pearwood.info
Sun Dec 2 08:43:24 EST 2018


On Mon, Dec 03, 2018 at 02:04:31AM +1300, Greg Ewing wrote:
> Chris Angelico wrote:
> >I can't help thinking that it will be extremely surprising to have the
> >length remain the same while the items get consumed.
> 
> That can be fixed. The following version raises an exception if
> you try to find the length after having used it as an iterator.

That's not really a "fix" as such, more of a violation of the principle 
of least astonishment. 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:

# This is okay, doesn't change the nature of the object.
for i in range(sys.maxint):
    try:
        print(mapview[i])
    except IndexError:
        break

# But this unexpectedly changes it from sized to unsized.
for x in mapview:
    break

That makes this object a fragile thing that can unexpectedly change from 
sized to unsized. 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.

Or... at least that's what the code is supposed to do, the code you give 
doesn't actually work that way:


> class MapView:
>     def __init__(self, func, *args):
>         self.func = func
>         self.args = args
>         self.iterator = None
>     def __len__(self):
>         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)
> 
> >>> a = [1, 2, 3, 4, 5]
> >>> b = [2, 3, 5]
> >>> m = MapView(pow, a, b)
> >>> print(next(m))
> 1
> >>> print(len(m))
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
>   File "/Users/greg/foo/mapview/mapview.py", line 12, in __len__
>     raise TypeError("Mapping iterator has no len()")
> TypeError: Mapping iterator has no len()

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.

I daresay you could jerry-rig something to "fix" this bug, but I think 
this is a poor API that tries to make a single type act like two 
conceptually different things at the same time.



-- 
Steve


More information about the Python-ideas mailing list