[Python-ideas] Control Flow - Never Executed Loop Body

Andrew Barnert abarnert at yahoo.com
Sun Mar 20 19:54:20 EDT 2016


On Mar 20, 2016, at 16:39, Vito De Tullio <vito.detullio at gmail.com> wrote:
> 
> Robert Collins wrote:
> 
>>> Maybe I'm missing something, but shoulnd't be like
>>> 
>>>    if seq:
>>>        for elem in seq:
>>>            do_stuff(elem)
>>>    else:
>>>        do_empty_seq_stuff()
>>> 
>>> a trivial code handling an empty list?
>> 
>> seq = iter([])
>> 
>> will cause your example to not run do_empty_seq_stuff().
> 
> oh, ok. sorry, didn't think about the iterator object.
> 
> 
> ...thinking about it, is there a reason why the boolean interpretation of an 
> empty iterator is true?
> 
> I know it's not trivial to know if an iterator is empty (still, there are a 
> pair of recipes online, like http://code.activestate.com/recipes/413614-testing-for-an-empty-iterator/ or the first part of the response at 
> http://stackoverflow.com/a/3114423/273593 ) but shoulnd't be more "pythonic" 
> to just have a "falsy" value for an "empty" iterator?

It's not just "not trivial". The only way to do it with full generality (working for, e.g., generators) is to wrap the Iterator in a "peekable iterator" type (or something similar but more powerful, like tee).

But that changes the semantics of the iterator: every time you consume element #n, it produces #n+1, not #n. And it's not hard to think of generators where that would be horribly inappropriate. For example, imagine a generator that's reading responses off a network socket. If you try to read response #n+1 when you haven't sent request #n+1 yet (because you're about to look at response #n), it'll fail, or block, or read incorrect information, or something else terrible.

And there are other problems. For example, what if the generator needs to be able to handle g.throw; if you've replaced iterable with chain([peeked], iterable) or similar, that won't work.

Of course the iterator protocol _could_ have been designed differently (consider C++, where iterators have separate operators to get the current value and to advance to the next one), which would have changed the design of generator functions and generator expressions and various other things that came later, so these particular problems would never have arisen. There are obvious advantages and disadvantages to each design. But Python chose one design a decade and a half ago, and a lot has been built on top of that design, and it's not going to change now. And, given that design, there's no way to make iterators peekable. Which means that it wouldn't be pythonic to make empty iterators falsey, because that's impossible to do without peeking, and I'm pretty sure the only reason the Zen doesn't say possible is better than impossible is that it's so obvious it doesn't need to be said. :)

It's also worth noting that what you're asking for is really just a special case of LBYL vs. EAFP. Usually, it's better to just use the object and then see what happened, rather than checking in advance what will happen if you use it. Sometimes you can't avoid LBYL, or it's better because of the specifics of what you're doing, but in general, EAFP is the "default" way of thinking in pythonic code.


More information about the Python-ideas mailing list