for/else statements considered harmful

Howdy! Was teaching a new user to Python the ropes a short while ago and ran into an interesting headspace problem: the for/else syntax fails the obviousness and consistency tests. When used in an if/else block the conditional code is executed if the conditional passes, and the else block is executed if the conditional fails. Compared to for loops where the for code is repeated and the else code executed if we "naturally fall off the loop". (The new user's reaction was "why the hoek would I ever use for/else?") I forked Python 3.3 to experiment with an alternate implementation that follows the logic of pass/fail implied by if/else: (and to refactor the stdlib, but that's a different issue ;) for x in range(20): if x > 10: break else: pass # we had no values to iterate finally: pass # we naturally fell off the loop It abuses finally (to avoid tying up a potentially common word as a reserved word like "done") but makes possible an important distinction without having to perform potentially expensive length calculations (which may not even be possible!) on the value being iterated: that is, handling the case where there were no values in the collection or returned by the generator. Templating engines generally implement this type of structure. Of course this type of breaking change in semantics puts this idea firmly into Python 4 land. I'll isolate the for/else/finally code from my fork and post a patch this week-end, hopefully. — Alice.

Alice Bevan–McGregor wrote:
Yes, I love for/else and while/else but regret the name. The else is conceptually unlike the else in if/else, and leads to the common confusion that the else suite if the iterable is empty.
+10000 :)
Sadly, yes. Where were you when Python 3.0 was still being planned? :)
Many thanks. -- Steven

If we could go back in time I would completely agree. But since we can't, flipping meaning of else would be too error inducing and therefore not at all likely. So at risk of bike shedding I would suggest for ... [ else not: ] else [ finally ] : If a context-sensitive keyword would work I'd go for something more like for ... [ else empty: ] else [ no match ] : This would not introduce any incompatibilities. --- Bruce (from my phone) On Jun 6, 2012 4:31 PM, "Alice Bevan–McGregor" <alice@gothcandy.com> wrote:

On Thu, Jun 7, 2012 at 9:58 AM, Bruce Leban <bruce@leapyear.org> wrote:
The meaning of the "else:" clause on for and while loops is actually much closer to the sense in "try/except/else" sense than it is to the sense in "if/else". Consider the following: for x in range(20): if x > 10: break else: # Reached the end of the loop As an approximate short hand for: class BreakLoop(Exception): pass try: for x in range(20): if x > 10: raise BreakLoop except BreakLoop: pass else: # Reached the end of the loop It's not implemented anything like that (and the analogy doesn't hold in many other respects), but in terms of the semantics of the respective else clauses it's an exact match. Part of the problem is that the "else:" clause on while loops is often explained as follows (and I've certainly been guilty of this), which I now think exacerbates the confusion rather than reducing it: The following code: x = 0 while x < 10: x += 1 if x == y: break else: # Made it to 10 Can be seen as equivalent to: x = 0 while 1: if x < 10: pass else: # Made it to 10 x += 1 if x == y: break This actually ends up reinforcing the erroneous connection to if statements, when we really need to be encouraging people to think of this clause in terms of try statements, with "break" playing the role of an exception being raised. So I think what we actually have is a documentation problem where we need to be actively encouraging the "while/else", "for/else" -> "try/except/else" link and discouraging any attempts to think of this construct in terms of if statements (as that is a clear recipe for confusion). If anything were to change at the language level, my preference would be to further reinforce the try/except/else connection by allowing an "except break" clause: for x in range(20): if x > 10: break except break: # Bailed out early else: # Reached the end of the loop To critique the *specific* proposal presented at the start of the thread, there are three main problems with it: 1. It doesn't match the expected semantics of a "finally:" clause. In try/finally the finally clause executes regardless of how the suite execution is terminated (whether via an exception, reaching the end of the suite, or leaving the suite early via a return, break or continue control flow statement). That is explicitly not the case here (as a loop's else clause only executes in the case of normal loop termination - which precisely matches the semantics of the else clause in try/except/else) 2. As Bruce pointed out, the meaning of the else: clause on loops can't be changed as it would break backwards compatibility with existing code 3. The post doesn't explain how the proposed change in semantics also makes sense for while loops Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Wed, Jun 6, 2012 at 9:53 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
If anything were to change at the language level, my preference would be
SNIP My preference would be for a new keyword: nobreak This would work well with for/else and while/else which would become for/nobreak and while/nobreak I think that anyone reading while ... .... nobreak: some statements would (more) immediately understand that "some statements" are going to be executed if no break occurred in the above block. But I doubt that something like this will ever be considered even though it could be introduced now without breaking any code (other than that which uses "nobreak" as a variable ... which should be rare) by making it first a duplicate of the for/else and while/else construction which would be slowly deprecated. Just my 0.02$ ... André

On Wed, Jun 6, 2012 at 5:53 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
I like this proposal, or perhaps while ...: ... with break: # Bailed out early else: # Reached the end of the loop ...which avoids any conceptual baggage associated with exception handling, at some risk of making people think of context managers. For what it's worth, I don't use the loop version of 'else' to avoid confusing myself (or the reader of my code). But in my experience the use case 'else' is intended to solve is probably less common than (a) checking whether the loop was ever entered, and (b) checking from within the loop body whether it is the first iteration. Nathan

On Wed, Jun 6, 2012 at 7:15 PM, MRAB <python@mrabarnett.plus.com> wrote:
I think the problem is that "break" doesn't sound like a positive, it sounds like a negative, and indeed it means we effectively *ignore* the rest of the list. So when you get to the "else" it's like an English double-negative, awkward to understand. Perhaps even more because you're effectively else-ing the break, not the for, so the indentation level even seems off. Backwards-compatibility issues aside, renaming "else" to "finally" sounds like a really great idea.

On Wed, Jun 06, 2012 at 07:20:07PM -0400, Alice Bevan–McGregor wrote:
-1 for me. The idea that finally is executed only when we naturally fall off the loop is weird. finally suggests that it will always be executed, like in a try/finally clause. I think the naming of else is weird but can be understood. If a change is a must I believe else should keep its semantic and simply be renamed except, but I am +0 on that. All in all the use cases would be extremely rare if existant. I've never actually seen a for/else or while/else block.

On 6/6/2012 7:20 PM, Alice Bevan–McGregor wrote:
I disagree. The else clause is executed when the condition (explicit in while loops, implicit in for loops) is false. Consider the following implementation of while loops in a lower-level pseudo-python: label startloop if condition: do_something() goto startloop else: do_else_stuff() This is *exactly* equivalent to while condition: do_something() else: do_else)_stuff() In fact, the absolute goto is how while is implemented in assembler languages, include CPython bytecode. If one converts a for-loop to a while-loop, you will see the same thing. CPython bytecode for for-loops is a little more condensed, with a higher level FOR_ITER code. It tries to get the next item if there is one and catches the exception and jumps if not. (It also handles and hides the fact that there are two iterator protocols.) But still, an absolute 'goto startloop' jump back up to FOR_ITER is added to the end of the 'if next' suite, just as with while-loops. -- Terry Jan Reedy

We had quite a lengthy discussion on for/else in October 2009 http://mail.python.org/pipermail/python-ideas/2009-October/thread.html#5924 Guido mentioned:
http://mail.python.org/pipermail/python-ideas/2009-October/006157.html Personally I'd prefer "if not break:" over "else:" but as we're stuck where we are today I'm just going to encourage people not to use the construct at all. Yuval

On Thu, Jun 7, 2012 at 10:57 AM, Devin Jeanpierre <jeanpierreda@gmail.com>wrote:
<opinion> For-else/while-else are confusing. During the previous discussion even the construct's proponents have fallen to its misleading nature. The word "else" alone just doesn't fit its role here no matter how intricate and carefully constructed an example is given to explain its nature or rationale. I believe using for/else will cause you and maintainers of your code to make more mistakes. </opinion> Yuval

On Thu, Jun 7, 2012 at 4:31 AM, Yuval Greenfield <ubershmekel@gmail.com> wrote:
I don't follow. What mistakes would people make? Why would they make them? Also, are you worried about people that read the documentation and know what for-else does, or the people that don't or haven't read this documentation? It's good practice to, when reading source code of an unfamiliar language, try to read up on things you haven't seen yet -- although sometimes context seems good enough. If you are afraid that this is someplace that context _seems_ good enough, but actually _isn't_, that would be something to worry about (although I don't feel that way). -- Devin

Devin Jeanpierre writes:
On Thu, Jun 7, 2012 at 4:31 AM, Yuval Greenfield <ubershmekel@gmail.com> wrote:
There was a long thread about a year ago on this list, where a couple of less experienced programmers and even a couple of people who have long since proven themselves reliable, gave code examples that obviously hadn't been tested.<wink/> There's a summary at: http://grokbase.com/t/python/python-ideas/09abg9k5fc/summary-of-for-else-thr... The reason they make such mistakes is that there's a strong association of "else" with "if-then-else", and for many people that seems to be somewhere between totally useless and actively misleading. For me, there are a number of reasonable mnemonics, a couple given in this thread, but IIRC the only idiom I found really plausible was def search_in_iterable(key, iter): for item in iter: if item == key: return some_function_of(item) else: return not_found_default

On Thu, Jun 7, 2012 at 1:50 PM, Stephen J. Turnbull <stephen@xemacs.org>wrote:
You don't need the "else" there. An equivalent: def search_in_iterable(key, iter): for item in iter: if item == key: return some_function_of(item) return not_found_default I'm not sure I understood what you meant but I'll assume that by "plausible"/"reasonable" you meant that it's a good example as to how for/else is misleading. Devin Jeanpierre Wrote:
On this issue I'm worried about all sentient programmers. Yuval

Yuval Greenfield writes:
*You* don't need it. *I* like it, because it expresses the fact that returning a default is a necessary complement to the for loop. While this is something of a TOOWTDI violation, there are cases where else is needed to express the semantics, as well (eg, if the first return statement is replaced by "process(item); break").

On Fri, Jun 8, 2012 at 4:04 AM, Stephen J. Turnbull <stephen@xemacs.org> wrote:
I've never been sure of what is good style here. It's comparable to these two things: def foo(): if bar(): return baz return quux def foo2(): if bar(): return baz else: return quux Is there some well-accepted rule of which to use? -- Devin

On Fri, Jun 8, 2012 at 8:08 AM, Steven D'Aprano <steve@pearwood.info> wrote:
It's indeed a very subtle choice, and for simple examples it usually doesn't much matter. I tend to like #1 better if "then" block is small (especially an error exit or some other "early return" like a cache hit) and the "else" block is more substantial -- it saves an indentation level. (I also sometimes reverse the sense of the test just to get the smaller block first, for this reason.) When there are a bunch of elif clauses each ending with return (e.g. emulating a switch) I think it makes more sense to use "else" for the final clause, for symmetry. So maybe my gut rule is that if the clauses are roughly symmetrical, use the else, but if there is significant asymmetry, don't bother. -- --Guido van Rossum (python.org/~guido)

The rule I have adopted is to omit unneeded after-if else to separate preamble stuff -- argument checking -- from the core algorithm, but leave it when branching is an essential part of the algorithm. My idea is that if the top level structure of the algorithm is an alternation, then the code should say so without the reader having to examine the contents of the branch. Example: floating-point square root def fsqrt(x): if not isinstance(x, float): raise TypeError elif x < 0: raise ValueError # Now we are ready for the real algorithm if x > 1.0: return fsqrt(1/x) else: # iterate return result Omission of elses can definitely be taken too far. There is in the C codebase code roughly with this outline: if expression: # about 15 line with at least 2 nested ifs (with else omitted) # and at least 3 codepaths ending in return calculation for else but with else omitted It takes far longer for each reader to examine if block to determine the the following block is really an else block that it would have taken one writer to just put in the "} else {" Also, some editor allow collapsing of indented blocks, but one cannot do that if else is omitted. Of course, it is routine to omit unneeded else after loops. -- Terry Jan Reedy

On Thu, Jun 7, 2012 at 6:50 AM, Stephen J. Turnbull <stephen@xemacs.org> wrote:
This is disappointing. for-else is simple, even if it has an ambiguous name.
I know it's really bad form to shift goalposts, but I can't help but offer an alternative hypothesis: What if it isn't that else is confusing, but that use of else is rare? People have lots of silly beliefs about things they never use, or haven't used in a very long time.
For me, there are a number of reasonable mnemonics, a couple given in this thread, but IIRC the only idiom I found really plausible was
I think of "else" as a collective/delayed else to the if statement in the body of the loop (which is almost always present). This only works for for loops though. Pretty much every single for-else has almost exactly the same form, though, so... it's pretty easy to use specialized models like that. :) -- Devin

On Thu, Jun 7, 2012 at 10:01 PM, Devin Jeanpierre <jeanpierreda@gmail.com> wrote:
FWIW, I just added the following paragraph to the relevant section of the Python tutorial in 2.7, 3.2 and 3.3: ================= When used with a loop, the ``else`` clause has more in common with the ``else`` clause of a :keyword:`try` statement than it does that of :keyword:`if` statements: a :keyword:`try` statement's ``else`` clause runs when no exception occurs, and a loop's ``else`` clause runs when no ``break`` occurs. For more on the :keyword:`try` statement and exceptions, see :ref:`tut-handling`. ================= The new text should appear in the respective online versions as part of the next daily docs rebuild. It may not help much, but it won't hurt, and the "exceptional else" is a much better parallel than trying to make loop else clauses fit the "conditional else" mental model. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Devin Jeanpierre wrote:
I use the for/else and while/else constructs, and still get them wrong -- the association with if/else is very strong for me, and my usage pattern is more along the lines of "if this iterable was empty at the start...". I appreciate the correlation with except/else, and the failed search idea -- those should help me keep these straight even before my tests fail. ;) ~Ethan~

So the subject of the thread seems to hold true. Average developers are confused by the current semantic (a problem that needs more than abstract p-code to correct) to the point of actively avoiding use of the structure. I agree, however, that breaking all existing code is probably bad. ;) On 2012-06-07 00:53:22 +0000, Nick Coghlan said:
Seems a not insignifigant number of readers got fixated on the alternate keyword for the current behaviour of else (finally in my example) and ignored or misinterpreted the -really important part- of being able to detect if the loop was skipped (no iterations performed; else in my example). Being able to have a block executed if the loop is never entered is vitally important so you can avoid expensive or potentially impossible length checks on the iterator before the loop. Take this example: sock = lsock.accept() for chunk in iter(partial(sock.recv, 4096), ''): pass # do something with the chunk else: pass # no data recieved before client hangup! Using a temporary varable to simulate this is… unfortunate. sock = lsock.accept() has_data = False for chunk in iter(partial(sock.recv, 4096), ''): has_data = True pass # do something with the chunk if not has_data: pass # no data recieved before client hangup! empty woud be a good keyword to preserve the existing meaning of else, but I'm pretty sure that's a fairly common variable name. :/ — Alice.

On Thu, Jun 7, 2012 at 11:06 PM, Alice Bevan–McGregor <alice@gothcandy.com> wrote:
Yeah, it's usually fairly important on here to separate out "this is the problem I see" from "this is a proposed solution". Getting agreement on the former is usually easier than the latter, since there are so many additional constraints that come into play when it comes to considering solutions. And if we can't even reach agreement that a problem needs to be solved, then talking about solution details isn't especially productive (although it can be fun to speculate about the possibilities anyway). FWIW, I usually solve this particular problem with for loops by using the iteration variable itself to hold a sentinel value: sock = lsock.accept() chunk = None for chunk in iter(partial(sock.recv, 4096), ''): pass # do something with the chunk if chunk is None: pass # no data recieved before client hangup! If "None" is a possible value in the iterable, then I'll use a dedicated sentinel value instead: var = sentinel = object() for var in iterable: ... if var is sentinel: ... I've never found either of those constructs ugly enough to particularly want dedicated syntax to replace it, and the availability of this approach is what makes it especially difficult to push for dedicated syntactic support (since all that can really be saved is the assignment that sets up the sentinel value). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan wrote:
This seems like a good work-around (meaning: I'll definitely use it, thanks!), but it does not address the confusion issues. I think the main problem with the current while/else, for/else is two-fold: 1) we have two failure states (empty from the start, and desired result not met), and 2) even though the else is more similar to the else in try/except/else, it is formatted *just like* the if/else. Perhaps the solution is to enhance for and while with except? sock = lsock.accept() for chunk in iter(partial(sock.recv, 4096), ''): pass # do something with the chunk except: pass # no data recieved before client hangup! else: pass # wrap-up processing on chunks ~Ethan~

On Thu, 07 Jun 2012 06:52:49 -0700 Ethan Furman <ethan@stoneleaf.us> wrote:
I'd say we have 1.5 failure states, because the desired result is not met in both cases. In my experience, the general case (that else handles) is more common than the special case of the iterator being empty.
Calling it "wrap-up processing" seems likely to cause people to think about it as meaning "finally". But if the else clause is not executed if the except clause is (as done by try/except/else), then there's no longer an easy way to describe it. It seems like adding an except would change the conditions under which the else clause is executed (unlike try/except/else), as otherwise there's no easy way capture the current behavior, where else is executed whenever there are no chunks left to process. But that kind of things seems like a way to introduce bugs. <mike -- Mike Meyer <mwm@mired.org> http://www.mired.org/ Independent Software developer/SCM consultant, email for more information. O< ascii ribbon campaign - stop html mail - www.asciiribbon.org

On 2012-06-07 15:30:11 +0000, Mike Meyer said:
Well, how about: for <var> in <iterable>: pass # process each <var> except: # no arguments! pass # nothing to process else: pass # fell through finally: pass # regardless of break/fallthrough/empty Now for loops perfectly match try/except/else/finally! >:D (Like exception handling, finally would be called even with an inner return from any of the prior sections.) — Alice.

On Thu, 7 Jun 2012 11:52:10 -0400 Alice Bevan–McGregor <alice@gothcandy.com> wrote:
For for (and don't forget while) loops, finally is pointless. It's the same as code after the loop. For try, finally runs even if there's an exception, which isn't true of that code. <mike -- Mike Meyer <mwm@mired.org> http://www.mired.org/ Independent Software developer/SCM consultant, email for more information. O< ascii ribbon campaign - stop html mail - www.asciiribbon.org

On 2012-06-07 16:29:01 +0000, Mike Meyer said:
I really should use parenthesis less as obviously people don't read the content between them. (Not just you, I'm afraid! ;^) If it weren't a useful feature (for/empty) I'm unsure as to why so many template engines implement it even though in most of them you _can_ utilize a sentinel value; at least, in the ones that allow embedded Python code. Alas, the BDFL has spoken, however. (Getting shot down was not unexpected despite the occasional +1000. ;) — Alice.

On 7 June 2012 00:20, Alice Bevan–McGregor <alice@gothcandy.com> wrote:
My solution: don't talk about a for/else construct, but talk about a for/break/else block instead. Then the semantics become obvious again.
I think your use of finally is as unfortunate as the current use of else: usually, finally is *always* executed, irrespective of what happened in the try block. Your new use goes against that. Cheers, -- Arnaud

On Wed, Jun 6, 2012 at 7:20 PM, Alice Bevan–McGregor <alice@gothcandy.com> wrote:
I read it not as for/else and while/else, but break/else and this has been a much more natural framing for myself and those I've used the framing to explain the behavior to.
-- Read my blog! I depend on your acceptance of my opinion! I am interesting! http://techblog.ironfroggy.com/ Follow me if you're into that sort of thing: http://www.twitter.com/ironfroggy

Alice Bevan–McGregor wrote:
Yes, I love for/else and while/else but regret the name. The else is conceptually unlike the else in if/else, and leads to the common confusion that the else suite if the iterable is empty.
+10000 :)
Sadly, yes. Where were you when Python 3.0 was still being planned? :)
Many thanks. -- Steven

If we could go back in time I would completely agree. But since we can't, flipping meaning of else would be too error inducing and therefore not at all likely. So at risk of bike shedding I would suggest for ... [ else not: ] else [ finally ] : If a context-sensitive keyword would work I'd go for something more like for ... [ else empty: ] else [ no match ] : This would not introduce any incompatibilities. --- Bruce (from my phone) On Jun 6, 2012 4:31 PM, "Alice Bevan–McGregor" <alice@gothcandy.com> wrote:

On Thu, Jun 7, 2012 at 9:58 AM, Bruce Leban <bruce@leapyear.org> wrote:
The meaning of the "else:" clause on for and while loops is actually much closer to the sense in "try/except/else" sense than it is to the sense in "if/else". Consider the following: for x in range(20): if x > 10: break else: # Reached the end of the loop As an approximate short hand for: class BreakLoop(Exception): pass try: for x in range(20): if x > 10: raise BreakLoop except BreakLoop: pass else: # Reached the end of the loop It's not implemented anything like that (and the analogy doesn't hold in many other respects), but in terms of the semantics of the respective else clauses it's an exact match. Part of the problem is that the "else:" clause on while loops is often explained as follows (and I've certainly been guilty of this), which I now think exacerbates the confusion rather than reducing it: The following code: x = 0 while x < 10: x += 1 if x == y: break else: # Made it to 10 Can be seen as equivalent to: x = 0 while 1: if x < 10: pass else: # Made it to 10 x += 1 if x == y: break This actually ends up reinforcing the erroneous connection to if statements, when we really need to be encouraging people to think of this clause in terms of try statements, with "break" playing the role of an exception being raised. So I think what we actually have is a documentation problem where we need to be actively encouraging the "while/else", "for/else" -> "try/except/else" link and discouraging any attempts to think of this construct in terms of if statements (as that is a clear recipe for confusion). If anything were to change at the language level, my preference would be to further reinforce the try/except/else connection by allowing an "except break" clause: for x in range(20): if x > 10: break except break: # Bailed out early else: # Reached the end of the loop To critique the *specific* proposal presented at the start of the thread, there are three main problems with it: 1. It doesn't match the expected semantics of a "finally:" clause. In try/finally the finally clause executes regardless of how the suite execution is terminated (whether via an exception, reaching the end of the suite, or leaving the suite early via a return, break or continue control flow statement). That is explicitly not the case here (as a loop's else clause only executes in the case of normal loop termination - which precisely matches the semantics of the else clause in try/except/else) 2. As Bruce pointed out, the meaning of the else: clause on loops can't be changed as it would break backwards compatibility with existing code 3. The post doesn't explain how the proposed change in semantics also makes sense for while loops Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Wed, Jun 6, 2012 at 9:53 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
If anything were to change at the language level, my preference would be
SNIP My preference would be for a new keyword: nobreak This would work well with for/else and while/else which would become for/nobreak and while/nobreak I think that anyone reading while ... .... nobreak: some statements would (more) immediately understand that "some statements" are going to be executed if no break occurred in the above block. But I doubt that something like this will ever be considered even though it could be introduced now without breaking any code (other than that which uses "nobreak" as a variable ... which should be rare) by making it first a duplicate of the for/else and while/else construction which would be slowly deprecated. Just my 0.02$ ... André

On Wed, Jun 6, 2012 at 5:53 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
I like this proposal, or perhaps while ...: ... with break: # Bailed out early else: # Reached the end of the loop ...which avoids any conceptual baggage associated with exception handling, at some risk of making people think of context managers. For what it's worth, I don't use the loop version of 'else' to avoid confusing myself (or the reader of my code). But in my experience the use case 'else' is intended to solve is probably less common than (a) checking whether the loop was ever entered, and (b) checking from within the loop body whether it is the first iteration. Nathan

On Wed, Jun 6, 2012 at 7:15 PM, MRAB <python@mrabarnett.plus.com> wrote:
I think the problem is that "break" doesn't sound like a positive, it sounds like a negative, and indeed it means we effectively *ignore* the rest of the list. So when you get to the "else" it's like an English double-negative, awkward to understand. Perhaps even more because you're effectively else-ing the break, not the for, so the indentation level even seems off. Backwards-compatibility issues aside, renaming "else" to "finally" sounds like a really great idea.

On Wed, Jun 06, 2012 at 07:20:07PM -0400, Alice Bevan–McGregor wrote:
-1 for me. The idea that finally is executed only when we naturally fall off the loop is weird. finally suggests that it will always be executed, like in a try/finally clause. I think the naming of else is weird but can be understood. If a change is a must I believe else should keep its semantic and simply be renamed except, but I am +0 on that. All in all the use cases would be extremely rare if existant. I've never actually seen a for/else or while/else block.

On 6/6/2012 7:20 PM, Alice Bevan–McGregor wrote:
I disagree. The else clause is executed when the condition (explicit in while loops, implicit in for loops) is false. Consider the following implementation of while loops in a lower-level pseudo-python: label startloop if condition: do_something() goto startloop else: do_else_stuff() This is *exactly* equivalent to while condition: do_something() else: do_else)_stuff() In fact, the absolute goto is how while is implemented in assembler languages, include CPython bytecode. If one converts a for-loop to a while-loop, you will see the same thing. CPython bytecode for for-loops is a little more condensed, with a higher level FOR_ITER code. It tries to get the next item if there is one and catches the exception and jumps if not. (It also handles and hides the fact that there are two iterator protocols.) But still, an absolute 'goto startloop' jump back up to FOR_ITER is added to the end of the 'if next' suite, just as with while-loops. -- Terry Jan Reedy

We had quite a lengthy discussion on for/else in October 2009 http://mail.python.org/pipermail/python-ideas/2009-October/thread.html#5924 Guido mentioned:
http://mail.python.org/pipermail/python-ideas/2009-October/006157.html Personally I'd prefer "if not break:" over "else:" but as we're stuck where we are today I'm just going to encourage people not to use the construct at all. Yuval

On Thu, Jun 7, 2012 at 10:57 AM, Devin Jeanpierre <jeanpierreda@gmail.com>wrote:
<opinion> For-else/while-else are confusing. During the previous discussion even the construct's proponents have fallen to its misleading nature. The word "else" alone just doesn't fit its role here no matter how intricate and carefully constructed an example is given to explain its nature or rationale. I believe using for/else will cause you and maintainers of your code to make more mistakes. </opinion> Yuval

On Thu, Jun 7, 2012 at 4:31 AM, Yuval Greenfield <ubershmekel@gmail.com> wrote:
I don't follow. What mistakes would people make? Why would they make them? Also, are you worried about people that read the documentation and know what for-else does, or the people that don't or haven't read this documentation? It's good practice to, when reading source code of an unfamiliar language, try to read up on things you haven't seen yet -- although sometimes context seems good enough. If you are afraid that this is someplace that context _seems_ good enough, but actually _isn't_, that would be something to worry about (although I don't feel that way). -- Devin

Devin Jeanpierre writes:
On Thu, Jun 7, 2012 at 4:31 AM, Yuval Greenfield <ubershmekel@gmail.com> wrote:
There was a long thread about a year ago on this list, where a couple of less experienced programmers and even a couple of people who have long since proven themselves reliable, gave code examples that obviously hadn't been tested.<wink/> There's a summary at: http://grokbase.com/t/python/python-ideas/09abg9k5fc/summary-of-for-else-thr... The reason they make such mistakes is that there's a strong association of "else" with "if-then-else", and for many people that seems to be somewhere between totally useless and actively misleading. For me, there are a number of reasonable mnemonics, a couple given in this thread, but IIRC the only idiom I found really plausible was def search_in_iterable(key, iter): for item in iter: if item == key: return some_function_of(item) else: return not_found_default

On Thu, Jun 7, 2012 at 1:50 PM, Stephen J. Turnbull <stephen@xemacs.org>wrote:
You don't need the "else" there. An equivalent: def search_in_iterable(key, iter): for item in iter: if item == key: return some_function_of(item) return not_found_default I'm not sure I understood what you meant but I'll assume that by "plausible"/"reasonable" you meant that it's a good example as to how for/else is misleading. Devin Jeanpierre Wrote:
On this issue I'm worried about all sentient programmers. Yuval

Yuval Greenfield writes:
*You* don't need it. *I* like it, because it expresses the fact that returning a default is a necessary complement to the for loop. While this is something of a TOOWTDI violation, there are cases where else is needed to express the semantics, as well (eg, if the first return statement is replaced by "process(item); break").

On Fri, Jun 8, 2012 at 4:04 AM, Stephen J. Turnbull <stephen@xemacs.org> wrote:
I've never been sure of what is good style here. It's comparable to these two things: def foo(): if bar(): return baz return quux def foo2(): if bar(): return baz else: return quux Is there some well-accepted rule of which to use? -- Devin

On Fri, Jun 8, 2012 at 8:08 AM, Steven D'Aprano <steve@pearwood.info> wrote:
It's indeed a very subtle choice, and for simple examples it usually doesn't much matter. I tend to like #1 better if "then" block is small (especially an error exit or some other "early return" like a cache hit) and the "else" block is more substantial -- it saves an indentation level. (I also sometimes reverse the sense of the test just to get the smaller block first, for this reason.) When there are a bunch of elif clauses each ending with return (e.g. emulating a switch) I think it makes more sense to use "else" for the final clause, for symmetry. So maybe my gut rule is that if the clauses are roughly symmetrical, use the else, but if there is significant asymmetry, don't bother. -- --Guido van Rossum (python.org/~guido)

The rule I have adopted is to omit unneeded after-if else to separate preamble stuff -- argument checking -- from the core algorithm, but leave it when branching is an essential part of the algorithm. My idea is that if the top level structure of the algorithm is an alternation, then the code should say so without the reader having to examine the contents of the branch. Example: floating-point square root def fsqrt(x): if not isinstance(x, float): raise TypeError elif x < 0: raise ValueError # Now we are ready for the real algorithm if x > 1.0: return fsqrt(1/x) else: # iterate return result Omission of elses can definitely be taken too far. There is in the C codebase code roughly with this outline: if expression: # about 15 line with at least 2 nested ifs (with else omitted) # and at least 3 codepaths ending in return calculation for else but with else omitted It takes far longer for each reader to examine if block to determine the the following block is really an else block that it would have taken one writer to just put in the "} else {" Also, some editor allow collapsing of indented blocks, but one cannot do that if else is omitted. Of course, it is routine to omit unneeded else after loops. -- Terry Jan Reedy

On Thu, Jun 7, 2012 at 6:50 AM, Stephen J. Turnbull <stephen@xemacs.org> wrote:
This is disappointing. for-else is simple, even if it has an ambiguous name.
I know it's really bad form to shift goalposts, but I can't help but offer an alternative hypothesis: What if it isn't that else is confusing, but that use of else is rare? People have lots of silly beliefs about things they never use, or haven't used in a very long time.
For me, there are a number of reasonable mnemonics, a couple given in this thread, but IIRC the only idiom I found really plausible was
I think of "else" as a collective/delayed else to the if statement in the body of the loop (which is almost always present). This only works for for loops though. Pretty much every single for-else has almost exactly the same form, though, so... it's pretty easy to use specialized models like that. :) -- Devin

On Thu, Jun 7, 2012 at 10:01 PM, Devin Jeanpierre <jeanpierreda@gmail.com> wrote:
FWIW, I just added the following paragraph to the relevant section of the Python tutorial in 2.7, 3.2 and 3.3: ================= When used with a loop, the ``else`` clause has more in common with the ``else`` clause of a :keyword:`try` statement than it does that of :keyword:`if` statements: a :keyword:`try` statement's ``else`` clause runs when no exception occurs, and a loop's ``else`` clause runs when no ``break`` occurs. For more on the :keyword:`try` statement and exceptions, see :ref:`tut-handling`. ================= The new text should appear in the respective online versions as part of the next daily docs rebuild. It may not help much, but it won't hurt, and the "exceptional else" is a much better parallel than trying to make loop else clauses fit the "conditional else" mental model. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Devin Jeanpierre wrote:
I use the for/else and while/else constructs, and still get them wrong -- the association with if/else is very strong for me, and my usage pattern is more along the lines of "if this iterable was empty at the start...". I appreciate the correlation with except/else, and the failed search idea -- those should help me keep these straight even before my tests fail. ;) ~Ethan~

So the subject of the thread seems to hold true. Average developers are confused by the current semantic (a problem that needs more than abstract p-code to correct) to the point of actively avoiding use of the structure. I agree, however, that breaking all existing code is probably bad. ;) On 2012-06-07 00:53:22 +0000, Nick Coghlan said:
Seems a not insignifigant number of readers got fixated on the alternate keyword for the current behaviour of else (finally in my example) and ignored or misinterpreted the -really important part- of being able to detect if the loop was skipped (no iterations performed; else in my example). Being able to have a block executed if the loop is never entered is vitally important so you can avoid expensive or potentially impossible length checks on the iterator before the loop. Take this example: sock = lsock.accept() for chunk in iter(partial(sock.recv, 4096), ''): pass # do something with the chunk else: pass # no data recieved before client hangup! Using a temporary varable to simulate this is… unfortunate. sock = lsock.accept() has_data = False for chunk in iter(partial(sock.recv, 4096), ''): has_data = True pass # do something with the chunk if not has_data: pass # no data recieved before client hangup! empty woud be a good keyword to preserve the existing meaning of else, but I'm pretty sure that's a fairly common variable name. :/ — Alice.

On Thu, Jun 7, 2012 at 11:06 PM, Alice Bevan–McGregor <alice@gothcandy.com> wrote:
Yeah, it's usually fairly important on here to separate out "this is the problem I see" from "this is a proposed solution". Getting agreement on the former is usually easier than the latter, since there are so many additional constraints that come into play when it comes to considering solutions. And if we can't even reach agreement that a problem needs to be solved, then talking about solution details isn't especially productive (although it can be fun to speculate about the possibilities anyway). FWIW, I usually solve this particular problem with for loops by using the iteration variable itself to hold a sentinel value: sock = lsock.accept() chunk = None for chunk in iter(partial(sock.recv, 4096), ''): pass # do something with the chunk if chunk is None: pass # no data recieved before client hangup! If "None" is a possible value in the iterable, then I'll use a dedicated sentinel value instead: var = sentinel = object() for var in iterable: ... if var is sentinel: ... I've never found either of those constructs ugly enough to particularly want dedicated syntax to replace it, and the availability of this approach is what makes it especially difficult to push for dedicated syntactic support (since all that can really be saved is the assignment that sets up the sentinel value). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan wrote:
This seems like a good work-around (meaning: I'll definitely use it, thanks!), but it does not address the confusion issues. I think the main problem with the current while/else, for/else is two-fold: 1) we have two failure states (empty from the start, and desired result not met), and 2) even though the else is more similar to the else in try/except/else, it is formatted *just like* the if/else. Perhaps the solution is to enhance for and while with except? sock = lsock.accept() for chunk in iter(partial(sock.recv, 4096), ''): pass # do something with the chunk except: pass # no data recieved before client hangup! else: pass # wrap-up processing on chunks ~Ethan~

On Thu, 07 Jun 2012 06:52:49 -0700 Ethan Furman <ethan@stoneleaf.us> wrote:
I'd say we have 1.5 failure states, because the desired result is not met in both cases. In my experience, the general case (that else handles) is more common than the special case of the iterator being empty.
Calling it "wrap-up processing" seems likely to cause people to think about it as meaning "finally". But if the else clause is not executed if the except clause is (as done by try/except/else), then there's no longer an easy way to describe it. It seems like adding an except would change the conditions under which the else clause is executed (unlike try/except/else), as otherwise there's no easy way capture the current behavior, where else is executed whenever there are no chunks left to process. But that kind of things seems like a way to introduce bugs. <mike -- Mike Meyer <mwm@mired.org> http://www.mired.org/ Independent Software developer/SCM consultant, email for more information. O< ascii ribbon campaign - stop html mail - www.asciiribbon.org

On 2012-06-07 15:30:11 +0000, Mike Meyer said:
Well, how about: for <var> in <iterable>: pass # process each <var> except: # no arguments! pass # nothing to process else: pass # fell through finally: pass # regardless of break/fallthrough/empty Now for loops perfectly match try/except/else/finally! >:D (Like exception handling, finally would be called even with an inner return from any of the prior sections.) — Alice.

On Thu, 7 Jun 2012 11:52:10 -0400 Alice Bevan–McGregor <alice@gothcandy.com> wrote:
For for (and don't forget while) loops, finally is pointless. It's the same as code after the loop. For try, finally runs even if there's an exception, which isn't true of that code. <mike -- Mike Meyer <mwm@mired.org> http://www.mired.org/ Independent Software developer/SCM consultant, email for more information. O< ascii ribbon campaign - stop html mail - www.asciiribbon.org

On 2012-06-07 16:29:01 +0000, Mike Meyer said:
I really should use parenthesis less as obviously people don't read the content between them. (Not just you, I'm afraid! ;^) If it weren't a useful feature (for/empty) I'm unsure as to why so many template engines implement it even though in most of them you _can_ utilize a sentinel value; at least, in the ones that allow embedded Python code. Alas, the BDFL has spoken, however. (Getting shot down was not unexpected despite the occasional +1000. ;) — Alice.

On 7 June 2012 00:20, Alice Bevan–McGregor <alice@gothcandy.com> wrote:
My solution: don't talk about a for/else construct, but talk about a for/break/else block instead. Then the semantics become obvious again.
I think your use of finally is as unfortunate as the current use of else: usually, finally is *always* executed, irrespective of what happened in the try block. Your new use goes against that. Cheers, -- Arnaud

On Wed, Jun 6, 2012 at 7:20 PM, Alice Bevan–McGregor <alice@gothcandy.com> wrote:
I read it not as for/else and while/else, but break/else and this has been a much more natural framing for myself and those I've used the framing to explain the behavior to.
-- Read my blog! I depend on your acceptance of my opinion! I am interesting! http://techblog.ironfroggy.com/ Follow me if you're into that sort of thing: http://www.twitter.com/ironfroggy
participants (19)
-
Alice Bevan–McGregor
-
Andre Roberge
-
Arnaud Delobelle
-
Bruce Leban
-
Calvin Spealman
-
Carl M. Johnson
-
Devin Jeanpierre
-
Don Spaulding
-
Ethan Furman
-
Guido van Rossum
-
Mike Meyer
-
MRAB
-
Nathan Schneider
-
Nick Coghlan
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Terry Reedy
-
Westley Martínez
-
Yuval Greenfield