Python Iterables struggling using map() built-in

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun Dec 7 18:33:42 EST 2014


Chris Angelico wrote:

> On Mon, Dec 8, 2014 at 5:27 AM, Ian Kelly <ian.g.kelly at gmail.com> wrote:
>> On Dec 7, 2014 8:31 AM, "Ned Batchelder" <ned at nedbatchelder.com> wrote:
>>> NOTE: THIS EXAMPLE IS HORRIBLE.  This code is crazy-confusing, and
>>> should never have been used as an example of iteration. It layers at
>>> least three iterations on top of each other, making it very difficult to
>>> see what is
>>> going on.  It uses "while iters" where "while True" would do exactly the
>>> same thing (iters will never be false).
>>
>> That's not quite correct; the "while iters" actually guards against the
>> case where the passed args are empty. With no sub-iterators, no
>> StopIteration would ever be raised, and the result would be an infinite
>> generator of empty tuples. The "while iters" makes it return immediately
>> instead.
>>
>> So it seems this example is even more confusing than you thought.
> 
> I'm actually glad PEP 479 will break this kind of code. Gives a good
> excuse for rewriting it to be more readable.

What kind of code is that? Short, simple, Pythonic and elegant? :-)

Here's the code again, with indentation fixed:


def myzip(*args):
    iters = map(iter, args)
    while iters:
        res = [next(i) for i in iters]
        yield tuple(res)


That is *beautiful code*. It's written for Python 2, where map returns a
list, so the "while iters" line is morally equivalent to:

    while iters != [] and True


It would be even more beautiful if we get rid of the unnecessary temporary
variable:

def myzip(*args):
    iters = map(iter, args)
    while iters:
        yield tuple([next(i) for i in iters])


I think this function makes a good test to separate the masters from the
apprentices. No offence intended to Ned, who is a master, anyone can have a
bad day or a blind spot. 

If you can read this function and instantly tell how it works, that it is
bug-free and duplicates the behaviour of the built-in zip(), you're
probably Raymond Hettinger. If you can tell what it does but you have to
think about it for a minute or two before you understand why it works, you
can call yourself a Python master. If you have to sit down with the
interactive interpreter and experiment for a bit to understand it, you're
doing pretty well.

I do not believe that good code must be obviously right. It's okay for code
to be subtly right. Either is better than complicated code which contains
no obvious bugs.

How would we re-write this to work in the future Python 3.7? Unless I have
missed something, I think we could write it like this:

def myzip37(*args):
    iters = list(map(iter, args))
    while iters:
        try:
            yield tuple([next(i) for i in iters])
        except StopIteration:
            return


which I guess is not too horrible. If Python had never supported the current
behaviour, I'd probably be happy with this. But having seen how elegant
generators *can* be, the post-PEP 479 version will always look bloated and
clumsy to me, like Garfield next to a cheetah.




-- 
Steven




More information about the Python-list mailing list