Hacker News new | comments | show | ask | jobs | submit login
Python’s For - Else (pythontips.com)
469 points by federicoponzi 5 months ago | hide | past | web | favorite | 242 comments



The word else implies something different to what it does (IMHO at least). In and if/else clause, the else happens when the primary/expected thing doesn't happen. I would expect that for a loop, an else clause would happen when the loop does _not_ finish normally, or if they loop is empty. I'm sure it's a useful clause in certain cases, but the meaning of the word used is reversed. It might be better use 'then' or something like that:

  for i in l:
     print(i)
  then:
     print("iteration finished without breaking")


Here’s why (Hettinger):

> In 1991, “else” was the obvious choice because of the traditional way compilers implemented while-loops: pastebin.com/tY35CTJ4

That is, “if I haven’t finished the loop, GOTO the body.”

He says if he had a time machine he could tell Guido in the future we’re all using structured programming so no one will find “else” intuitive; call it “nobreak” instead: https://m.youtube.com/watch?v=OSGv2VnC0go#

Guido says if he had a time machine he would not have included the feature at all: https://mail.python.org/pipermail/python-ideas/2009-October/...

This whole thing was Knuth’s idea, not Guido’s: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.103...


> Guido says if he had a time machine he would not have included the feature at all

So deprecate it with a runtime warning and remove it after some years.

I wonder if there are some statistics about its usage. I guess it's very low.


> So deprecate it with a runtime warning and remove it after some years.

After the Python3 brouhaha, I think people are very cautious about making backwards incompatible changes to the language, even with a deprecation warning.

If it was done together with the rest of Python3 changes then it could have been approved, but not any more.

As Guido suggests, adding the warning to pylint and other style checkers, but keeping compatibility is much easier.


Why remove a feature that could be useful? That makes no sense.

Just because it isn't named in a way people find intuitive doesn't mean it isn't useful.


> Why remove a feature that could be useful? That makes no sense.

It makes sense in the way that a language which is strongly biased to readability and consistent expectations makes sense. It is also how a language which minimizes programmer error makes sense.

For languages that refuse to remove anything, and thus suffer from unnecessary challenges, see C++.


Several things have been removed from C++, including auto_ptr, exception specifications, unary/binary_functions, trigraphs.


I think the extreme difficulty and indeed controversy that the committee stared down when removing trigraphs, which are a useless feature that the vast majority of C++ programmers will never once encounter, speaks well to GP's point that C++ is very conservative in how it removes features. Maybe not JS-level careful. But still.


Of course, JS has a problem in this regard that while it looks like a language, it is also a (de facto) assembler for the browsers. So disabling anything in JS will break lots of sites which will never be fixed.

However as far as programming is concerned, many things have been deprecated through transpilers and linters (for example "==" throws a warning in ESlint).


Most legacy cpp features and language semantics will never be removed, despite what many prominent voices in the community think.


Unintuitive features that "could be useful" sounds like the antithesis of good design.


And it sounds like a common problem in the software industry.


>Why remove a feature that could be useful? That makes no sense.

Cleaning up a language "makes no sense"?

Besides "could be useful" can be said for anything. Features should prove their usefulness (and in this case Guido regrets even adding it), we don't just keep them around in the off chance that they might be. That's hoarding.


You want a major version bump on very widely used software just to remove something that causes a minor confusion on the rare occasion it is used? In what world does that sound like a good idea?

It's one thing to make a change like this along with a bunch of other breaking changes, but there is no way this justifies breakage by itself.


>You want a major version bump on very widely used software just to remove something that causes a minor confusion on the rare occasion it is used?

No, I want a deprecation warning, and an eventual removal when for 4.0 lands.

>In what world does that sound like a good idea?

In a world where we don't pile crap upon crap forever, like with POSIX, X-Windows, or C++.


There are reason those systems are still widespread in use today, strong compatibility is one of them.


There are also reasons those systems have tons of accidental complexity that frustrate users and wastes endless workhours. Strong compatibility is one of them.


> Why remove a feature that could be useful? That makes no sense.

Everything has a cost. In maintenance, in documentation, in support, in limiting the design space or constraining further development for other reasons (performance, for example).

Nothing is truly free. Everything has to be balanced regarding costs and benefits.


Here's the first sentence of the spec of the Scheme programming language:

"Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary."


It's interesting how Lisp languages get criticized for lack of maintainable because of their power to let you essentially add new features, but then every popular modern language with the exception of Go piles on new features over time.


Go is still relatively young.


That grand statement is made possible by banishing needed Scheme features into numerous SRFI documents, and into implementations.


I find it hard to argue it’s (the else block) any more useful than just using the `else` block’s logic in the try block.

    try:
        import uvloop
        asyncio.set_event_loop_policy(uvloop.EventLoopPolicy)
    except ImportError:
        pass
Does the handler for if the try succeeds need to be in an else, separated from the success logic?


Generally you can't be sure that the second statement does not also raise ImportError, where you do not want to catch it.


You can actually use else for try statements. Doesn't answer question but it's something I learned relatively recently and may help some others.

    try:
        import uvloop
        asyncio.set_event_loop_policy(uvloop.EventLoopPolicy)
    except ImportError:
        pass
    else:
        print("well that didn't work")


I'm not sure about what the message in that print() refers to (else fires if the import succeeded) so here's another example

    $ python3
    >>> try:
          1/0
        except ZeroDivisionError:
          print("exception")
        else:
          print("surprise")
        
    exception
    >>> try:
          1/1
        except ZeroDivisionError:
          print("exception")
        else:
          print("normal")
        
    1.0
    normal
The choice of the name else is unfortunate because it's activated under the same circumstances of a then branch of an "if". Maybe its name should really be "then". That would also look like a .then of a JavaScript Promise, which would make sense nowadays and was obviously impossible to foresee back in 1991. However the name "else" makes sense if it refers to "except": it's what happens when no "except" fire. I still don't understand why it should be "else" for a "for" that succeeded: it should be a "then" there.


In the case of a for loop, you are modeling "a for which did not break". The cases where you want that block are typically when you are looping through a list trying to find something, so "if I found it I broke, else do this default logic".


That's the intended use case but it works also when there is no break

    $ python3
    >>> for i in [1,2,3]:
          print(i)
        else:
          print("completed")
        
    1
    2
    3
    completed
That should be a "then", but admittedly a very useless one.

I agree that for the intended use case "else" is a better choice. It's like the else of the if that failed to find 2 in this code:

    >>> for i in [1,2,3]:
          if i == 2:
            break
        else:
          print("we didn't find 2")
        
Still "else" doesn't hint much at what's going on, especially if the body of the loop is long. Maybe "nobreak" would make it immediately understandable to everybody? However in the best Python tradition I would make it very explicit, remove the feature from the language and use boolean flags. They are less compact but easier to understand than a feature that (probably) very few people know about.

    >>> we_found_2 = False # this is a valid assertion now 
    >>> for i in [1,2,3]:
          if i == 2:
            we_found_2 = True
            break
        if not we_found_2:
          print("we didn't find 2")
That's also the only way to do it if there are multiple break in the loop on different conditions.


yeah you are right, it should be

print("well that's done")


By the way, try also has an else clause that behaves like the one in if https://docs.python.org/3/reference/compound_stmts.html


It behaves more like the main branch of “if” unless you construe “try” as “try to produce an exception”; outside of “if”, Python’s “else” pretty consistently implies exactly the wrong picture of normal flow.


I guess I wasn’t clear, because my point is generally that moving the logic to an else block in any supported control structures (if, for, try, ...) doesn’t necessarily make it clearer or cleaner.


Goto is also useful... See why now?


This comment contains more snark than content, but I'll reply anyway:

Goto is considered harmful, but it's important to recognize the context of that decision rather than parroting the idea. It's not an obvious idea and if the discussion had happened today with modern languages[0] instead it's not at all obvious the same conclusions would be reached.

This write-up does a pretty fair job of translating the original paper to something more modern without a lot of bias. http://david.tribble.com/text/goto.html

[0]: Modern languages of course learned from this paper, so that's not a fair comparison. In fact golang contains a goto, but it behaves well according to Dijkstra's concerns and occasionally results in more readable code. I would be shocked if Dijkstra took any issue with go's implementation.


Because readability counts, and id at the core of Python.


Actually I wouldn't like it deprecated, not because I use it often or like it very much. In fact, I myself found this feature counterintuitive at first brush.

Deprecating any feature without any strong reason makes me irritateted and I don't find any strong reason for doing so.


The thing to do here is deprecate 'for-else' in favor of 'for-default' which is much clearer like in a case block.

'for-else' never needs to be removed but the code cleanup will happen over time.


I whipped up something to test that: https://gist.github.com/gvx/667efcd263b18d313801c1b1ffa41509

Running that on my personal projects folder gives me the following result:

  for elses 60
  for loops 1815
  # modules 598
  # skipped 2


Thanks. I run your program on a couple of projects of a customer. I'm not the only developer.

    for elses 0
    for loops 428
    # modules 673
    # skipped 26

    for elses 99
    for loops 6058
    # modules 2016
    # skipped 286


Runtime warning on what channel without breaking compatibility?


You could print the warning out to STDERR when the Python runtime finishes parsing and before it executes your script.

This will likely break some people's workflows, but honestly, it's the de-facto way of notifying users of a deprecation in many Python frameworks (Gunicorn did this recently with gunicorn-paster vs gunicorn --paste).

They could also include this in a "What's new in Python (4?)" since this is unlikely to be introduced in a minor version bump.


I'm not categorically against deprecation warnings. Isn't gunicorn a command line tool? I can respect such warnings in a tool or even sometimes a library, but not in a language interpreter. The user should as closely as possible be in full control of the streams in those cases, and any standard error output that isn't an exception (i.e. either deliberate or resulting from a programmer error) should stay the hell off.

The proper way to handle this is of course to introduce a new semver major version. No need for deprecation warnings because incompatibility is expected.


If Guido would not have included the feature at all, why did he not remove it from Python 3? That would have been the perfect opportunity.


Python 3 had it's first release in 2008, that thread was from 2009. Probably didn't even really think about it before then. (the more you know)


> He says if he had a time machine he could tell Guido in the future we’re all using structured programming

Sounds like if people were not already coding C, C++ and Pascal when Python was invented...


I don’t like this feature of Python, but I have come to disagree with what you mention, even though I understand your point.

For example, from [0], it’s described as:

> “The else clause is only executed when your while condition becomes false. If you break out of the loop, or if an exception is raised, it won't be executed.”

So it is very analogous to an if / else statement: the “else” after a for or while loop directly means the conditional of the loop evaluated to False.

If the program never reaches a state in which the loop conditional evaluates to False, then the else block is not entered.

Just like: only when the conditional of an if statement evaluates to False will the else block be entered.

After I understood this, I started to think that break is really the more problematic thing, because it means more like a goto sort of jump, abandoning the status of the variables governing the loop condition.

But again, I don’t like these loop-else features. They definitely violate least surprise for most people reading the code.

[0]: < https://stackoverflow.com/questions/3295938/else-clause-on-p... >


If python used C style loops rather than pushing iterators/generators so hard, I would buy that reasoning. But where's the conditional statement in `for i in range(0,10)`?

Now, clearly there is a check to see if it's still producing values. But it's intentionally hidden from a surface-level reading of the code.

Naming a control structure `else` when it intentionally does not have a clear conditional tied to it is odd to say the least.

But it's worse than that- the else clause is supposed to be the normal flow of the program. Programming introductions (fresh in the minds of many who use Python as it's targeted as user-friendly) often stress the fact that in an if/else statement only one or the other block is executed. But this flips that idea upside-down.


It would be the implicit StopIteration exception from the resulting iterator, but I completely agree this can easily be seen as inconsistent / surprising, which is among the reasons why I don’t like it.


Thanks for the clarification, I don't use python very often myself. That does underscore that there's really not a normal conditional there though.


> So it is very analogous to an if / else statement: the “else” after a for or while loop directly means the conditional of the loop evaluated to False.

...how is that supposed to apply to "for i in l"? What is the conditional?

I don't think of python for statements as involving conditionals at all; you seem to be describing a while/else rather than a for/else.


> ...how is that supposed to apply to "for i in l"? What is the conditional?

Presumably there is a conditional in the loop body that decides whether or not to break:

  for i in l:
    if ...:
      break
Think of the else clause as being what to do if nothing takes the if in the body.

Same for while...else. You'd only be using an else on a while if there was something in the while that could break, so it is whether or not the conditional that controls that decision happens or not that determines whether the else path is taken.


Huh? Judging by every other comment in the thread, the point of for/else is that breaking does not trigger the else clause. The else clause is triggered when the loop condition is tested and fails... but Python's for doesn't have a loop condition.


No, an explicit break skips the else clause and the else clause is evaluated if and only if the iterator in the loop raises a StopIteration exception (i.e., it's exhausted). For example, this code will still evaluate the else clause even though the break statement is always true, because iter([]).next() throws StopIteration immediately:

    for i in []:
        if True: break
    else: print("Foo")
while this will not:

    for i in [1]
        if True: break
    else: print("Bar")
The "conditional" in a for loop is that the iterator (l in your example) raises a StopIteration exception. I don't really like calling it a conditional though, because it's actually handled as an exception. I guess you could think of the condition being "are there any items remaining in l? If 'no' then evaluate the else clause." I've always considered the else clause on a for loop's proper purpose being to catch erroneous cases, for instance if you try and loop over files in an empty directory. Or if you have no matches searching line by line over a file. Etc.


You're right, technically, but the way tzs thinks about it can still be correct if one thinks about it this way: the if statement in your first loop is mathematically true, but it is never executed because [] is of course empty. It's not about whether the inner if ...: break statement's conditional can evaluate to true.

A lazy way to think about it is that way, but as long as you remember that it's what occurs when there is no break it's fine.


My point is mainly that it's determined by the loop iterator, not the body of the loop; the else clause being skipped if you explicitly break is a consequence of this, not a cause. This is important because there is another case that can terminate the loop early- the else will not be evaluated if the loop body throws an exception (even if it is caught):

    try:
        for i in range(5):
            raise Exception
        else:
            print("Foo") # still won't be evaluated
    except:
        print("Loop aborted")
The vacuous case (iterating over []) is also kinda important if you're iterating over something of variable length in case you need to distinguish between "loop terminated normally" and "input was empty". "else" can't really tell these two cases apart.


That's a very good point. I guess to me, I've already internalized the "else stands for when the loop doesn't terminate early ever" but for people new to the language or feature it might be confusing. The fuzzy reasoning above was my original way of having it make sense in my head but I've since internalized the logic of how it really works.


Yeah the thing I see that sometimes confuses people is that they think "else" works kinda like "finally" or "__exit__" in a context manager, where it's guaranteed to be called even if an exception is raised. I don't think there's anything wrong with thinking about it like you say, just need to be aware of a few edge cases.

Else in a for loop is kinda obscure anyway, so its importance is marginal. It is really handy for a couple specific patterns, but I've only ever found myself using it a handful of times (and I write a lot of Python).


Think along the lines of "for (l = lst.iterate(); l != null; l.next())".


This is right; it’s the StopIteration exception (which is fundamental to what a for loop does in Python).

Not saying is absolves the for/while else syntax, but it’s still using the expected iteration constructs.


Wouldn't that imply that the equivalent of a while/else loop would be a for/except loop, not a for/else loop?


As a matter of trivia, probably not. The iteration mechanism just happens to check for StopIteration, and does not automatically handle any other type of Exception, which would require a usual try block.

So StopIteration can be thought of as an equivalent idea to “emptiness” of an iterator, either an empty container, an exhausted generator, etc.

And since emptiness is most often handled as falsiness in a bool context, it’s in some sense the natural notion of a “false” iteration condition.

This is all incredible mental gymnastics though. But (speaking as someone who loves Python) there’s unfortunately a lot of that in Python.


That's deeply "unpythonic"


I don't think it violates any important least-surprise principle. You might be surprised to see it used -- but you don't get surprised when the code behaves how the documentation says it will.


Least surprise doesn't mean everything is documented. It means things behave analogously to everything else


But language attempts to resemble spoken language precisely so we don't have to rely on the documentation alone. We use if/else, not "xan/ka" because we want to leverage the existing meaning of those words.

In the early stages of Perl 6 design I requested a for/else feature where the "else" block fired if and only if the for loop had never been entered...the opposite of how this works. Being literally the opposite of how I would write it qualifies as surprising to me.


Naming is useful for getting adoption but not when you already know the language. And in your case you might have been surprised, but that's because you'd take a guess at what the feature does instead of finding out.

(I don't think for/else is the best option, gotos are. It's just not a harmful feature, the way other aspects of Python are.)


I'm a strong python advocate and rarely use for/else for this exact reason. It makes more sense to me for it to be the other way (your way) around. I always add a comment on the else when I do too, to ensure that future readers don't make the same mistake.


I agree. It's seldom something I've used in my scripts/small projects for this exact reason.

It should also be noted that there is a `while-else` compound statement as well, wherein using a `break` immediately exits the loop, without executing the `else` clause [1].

[1] https://docs.python.org/3/reference/compound_stmts.html#the-...


Yeah, it really feels (almost in a physical sense) like an anti-pattern... I have to force my fingers to write it.


I’ve never found a compelling reason to use this construct (or the analogous `while...else` construct). But then again, I rarely find myself using `else` with `if` either...


The Handlebars template engine works the way you're talking about. The "each" helper loops through an array. You can put in an "else" block for when the array is empty.

  {{#each paragraphs}}
      <p>{{this}}</p>
  {{else}}
      <p class="empty">No content</p>
  {{/each}}
--- https://handlebarsjs.com/builtin_helpers.html


Django templates also have such a clause but it is named "empty" instead of "else".


I like this ternary approach.


> I would expect that for a loop, an else clause would happen when the loop does _not_ finish normally, or if they loop is empty

Suppose it worked like that and you had this code:

  for i in l:
    A()
    if B():
      break
    C()
  else:
    D()
Wouldn't that be equivalent to:

  for i in l:
    A()
    if B():
      D()
      break
    C()
What your suggested else is doing is providing a place to put code to execute after a break in the loop, but we already have a place for such code: immediately in front of the break in the loop.

The only place that comes to mind where this would be useful is situations where you there is more than one

  if ...:
    D()
    break
in the loop. With your suggested kind of else, you could move all the D()'s to an else after the loop.


It makes sense to me in the specific context of using it to respond to an item not being found in a list. In other contexts, I agree that the word is not a good fit.


When I thought about this, I first agreed, then I thought that from the perspective of the interpreter it is else as in checks the condition and then goes to else if the condition is false. So I think it is not so much unintuitive, as it is consistent in a not immediately intuitive way.


Agree completely. I think for-else is same as if-else in that if the condition satisfies then it executes the main loop and if not it executes the 'else'.


Yes, "then" would have been a better choice. But "then" isn't a keyword in Python.

"else" is used with try blocks to indicate the exception didn't take place. It makes more sense there, because it follows the "except" sections.


Agreed. I can see where the design is coming from, but this coupled with the reliance on indentation to delineate scope means that it’s really visually confusing — I expect the `else` to correspond to the `if` statement when I see it in the context of the given example. It’s not strictly ambiguous, but it’s certainly an impediment to readability.


I am not a native speaker but I think "done" or "finished" is better. "else" is a counterpart of "break". It assumes people must think of "break" whenever they see a "for", which is not always the case. We refer to this weirdness as Pythonic.


I like it! Makes perfect sense, it's short, and since then "belongs to" the for, it makes sense for a break or exception to break out of both. On the other hand, under your suggestion someone still has to learn the meaning of then, it's not fully explicit. Since "Explicit is better than Implicit", if we are not optimizing for brevity I have these two suggestions that are more explicit alternatives to your then suggestion.

--------

Suggestion 1 (horrific suggestion):

Call this keyword finally. This implies that no matter what the finally part will execute. This is like defining never as a synonym for true, so that if(never) always executes. Why would anyone ever do that!! Hardly any choice in a design is as bad as explicitly saying something different from what will happen. So this is a terrible suggestion.

--------

Suggestion 2 (good suggestion):

Call this keyword ifthru. (Thru being an abbreviation for through). As it says, this one will run if the loop completed. Another way to read it is if execution continued "through" the end.

So your example might read:

  for i in l:
     print(i)
  ifthru:
     print("iteration finished without breaking or exceptions")
This is extremely explicit about the condition. I picked it to be short while remaining explicit. The elif keyword is an existing precedent for mispelling for the sake of brevity.

It also alerts the reader that logically there is a condition here. After all, semantically every block could start with the "then" word in English. Semantically, when you break you could also be considered to be "through" however in this case the ifthru keyword wouldn't serve any purpose at all, since it could just be on the next line. The "if" alerts the reader that there's a condition here..

I hope you'll agree that my second suggestion is pretty good :)


The else makes perfect sense. The for loop keeps going as long as the condition is true. When the condition is false, it exits the loop without breaking. Else acts on the condition being false.


What you say implies that you’d be happy with

  while(condition()):
      ...
  else:
      ...
But that makes even less sense since `while` is semantically like an `if` with continuation. GP is right, it’s rarely used, because the syntax clashes with the natural reading of the code.


while ... else is a good comparison, yes. and it exists exactly like that in python: if the condition check turns false, the else branch is executed.


But the else fires in Python if the loop does not break. It sounds like you're saying the opposite. Even if you aren't, I think we can agree it's potentially confusing.


This is the correct place for it to do so. Break skips the check entirely. If the for loop exits normally, the condition was false.


You understand that it can be a confusing code construct though, right? Even in your comments I'm kind of confused at your meaning because you said:

> When the condition is false, it breaks.

And then you said:

> If the for loop exits normally, the condition was false.


I have updated that comment, a normal loop exit is not the same as a break.

I can understand how it can be confusing, but the inverse would not make any sense.

Another way to look at a for loop is a fancy while loop that sets a variable by some function. Every loop iteration that function is ran. If the function fails, the loop exits.

This is infact how they work. https://anandology.com/python-practice-book/iterators.html#t...

If next() fails, that is when the loop exits.


They probably used the word "else" to cut down on the number of reserved keywords, and for consistency with try / except / else.


It is consistent with the idea that it provides an alternative clause to a failing loop guard.

Using the much clearer C syntax as pseudo-code, suppose we have this:

  for (;;) {
    if (loop_guard()) {
      body;
    } else {
      alternative;
      break;
    }
  }
We can imagine an extension to while which lets us rewrite it like this:

  while (loop_guard()) {
    body;
  } else {
    alternative;
  }
Based on this framing, there is justification in calling it else.

The runs counter to an intuition which would like it to be this:

   if (loop_guard()) {
     while (loop_guard()) {
       body;
     }
   } else {
     alternative;
   }
I.e. not an alternative in the face of the failed loop guard on every iteration, but just the first loop guard test: an alternative to the loop when no iterations take place.


It always made sense to me since I think about these constructs as the loop searching for something (and breaking if it finds it) and the else running if nothing was found.

    for item in list:
        if search_condition:
            found_item=item
            break
    else:
        found_item=default_item


As an alternative, you could do this:

found_item = next((item for item in list if search_condition), default_item)


Fwiw my initial assumption (as a non python dev) was that the else statement would execute once the loop condition was false (including the case where loop condition was never true).


Honestly it is not a big deal, just remember the else is for the break, not the for. Then it reads naturally: for this break else do that.


I agree that the word 'else' is confusing here. To me, it suggests that one thing is done, or the other, but not both. Python's try..except..else follows this pattern, but for..else and while..else do not, for some reason.


Every time I go to use for...else, I need to look up what the behavior is, I can never remember if it's hitting the break or not hitting the break. That being said, python always tries to minimize the number of keywords, so re-using else makes more sense then introducing a whole new keyword for a niche usecase.


...and then you realize that's why you shouldn't use it, right?


The way I remember it is that it makes sense in loops that search for something in an iterable. Such loops break when they're successful, and so it makes sense for the code in the else clause to run when the loop was unsuccessful.


"Then" implies that the associated code will always run after the for loop completes, which isn't true either. Maybe "complete" is a decent choice. I'm sure language keywords are an issue here, too.


it should be called nobreak

that explains exactly what it does


I love Python's for-else feature. I've used it on a number of occasions, and have found it to be useful and to cut down on code.

However, I agree that the use of "else" is horribly confusing. When I teach "for-else" to students in my Python courses, they are often surprised by the functionality, and doubly surprised that the word "else" is used. I often joke that it would be a great feature, if only they had chosen something a bit more intuitive and memorable instead of "else", such as "if_we_exited_the_block_without_encountering_a_break". Slightly more seriously: While I realize that adding a keyword to Python isn't in the cards, a word such as "nobreak" would be easier for people to understand, and would stress the point of the functionality.

The fact that Emacs always tries to indent the "else" of a "for-else" to align with an "if" inside of the loop, rather than the "for" with which it matches, speaks volumes. I mean, if Emacs is confused, then how are we mere mortals supposed to remember?

So when I use "for-else", it's almost always in code that I am writing for myself, and that I don't expect others to read or maintain.

Don't forget that there's also "while-else", although I can't remember the last time I saw that actually used.


Agree with everything here! As a Python novice I found the name terrible, but nowadays I make use of it pretty frequently, and like the power which comes with it. I also use `try-except-else` (though I appreciate the "else" here has completely different semantics, I think it's something few people know about)


I would definitely expect a human to understand code intent (and indent) better than Emacs. Machine learning has some ways to go still. ;-)


Emacs is really not a reference in terms of code indentation. I expect classic IDE to be much more robust at it, since emacs uses regex most of the time to determine indentation (the javascript mode is the only exception I remember), whereas I imagine a full-blown python IDE will actually parse the code.


Yes the for...else is useful but for years it didn't do what I expected, which is execute when the sequence was empty. After reading the docs a half dozen times I finally internalized that the else actually means "not broken," aka "not found."

So now when I use it, I always put a comment next to it for myself and the next person:

    for item in items:
        if found:
            break
            
    else:  # no-break 
        print('not found.')


Honestly, I would just not use it wherever possible. It's a wart on the language, and there are plenty of semantically superior alternatives. I would rather wrap in function + use return over break, use a sentinel value, or raise an exception over using the else block.

If there's anything I've learned, it's that just because a language has a feature, doesn't mean it should be used.


Function + return seems like a good solution, but I'd rather not keep additional state or use `raise` (ie socially acceptable GOTO) when the language has a primitive for this.

You shouldn't use a feature just because a language has it, you should use it because it's a simple, well-defined primitive that's less complex than alternatives.


In fact, what it means is "execute this else when the loop guard fails" (regardless of whether the loop body has executed at all, or how many times). It is a legitimate alternate clause in the sense that it provides alternative behavior when the guard fails.

Except when the loop is infinite, or is broken prematurely, the loop guard will fail. Under for item in list, the implicit guard fails immediately when the list is empty. If the list isn't empty, it fails after the last item is iterated upon.

Thus here it also means "else if there are no more items to be processed".


> It is a legitimate alternate clause in the sense that it provides alternative behavior when the guard fails.

But the "guard fails" and the else is executed on every normal loop exit, except if the exit was done with break.

    for a in range( 1,3 ):
        print( a )
    else:
        print( "end" )
gives:

    1
    2
    end
It's an "alternative" only when there's break. In all other cases it is executed.

Even naming it "nobreak" would be confusing as without break it is always executed at the end. The clearer naming would be approximately "on_every_nonbreak_end_of_for"


> for years it didn't do what I expected, which is execute when the sequence was empty

It does though http://codepad.org/en4mzowh


Sorry, "^only execute when the sequence was empty".


https://twitpic.com/4a52sh

A survey on this feature was done at Pycon 2011. Maybe things have changed since then, but at the time, most people didn't understand what for/else did.

I think it's a bad construct because, as many have mentioned, a lot of people make the commonsense interpretation that the else block will only execute if the for block did not.

Note, the link above come via Jeremy Avnet in the comments of this blog post.

https://nedbatchelder.com/blog/201110/forelse.html


Personally, I'm really struggling with the idea which is underlying to this feature, and I find it relieving to find this article and survey result.

- Does the else realize that the loop contains an if block?

- How does a break statement relate to the StopIteration exception?

- How does the else block run if the range wasn't, technically, empty?


> the commonsense interpretation that the else block will only execute if the for block did not

That is exactly what it does. So when you explicitly break, the for body was executed. As for the poll, the first answer is not wrong. It's first option even, the question is vague enough. I wouldn't read that much into it, 33+24+9 (66%) people gave correct answer.


> So when you explicitly break, the for body was executed.

Maybe that is what is technically happening, I honestly don't know what that means.

I consider the body of a for statement to be execute for every loop, and for the loop to normally end when all values have been iterated. And else-clause is something that happens instead of the intended clause, which makes it very hard to fit in.

Reading through the comments here, there does seem to exist a mental model where the else makes sense. Something along the lines of

`if` there still is a value to assign to `i` do `loop-body`, `else` do `else-body`.

You can learn that, and it does make sense, but it is not the mental model the majority of people will build when they see a normal for loop in Python.


I think you're mistaken on both counts. The first answer is what I call the "commonsense interpretation" and it is incorrect about what python's for/else does.


I don't know what to tell you...

For break to be called explicitly it has to enter the loop body - there is no other way. The break only works in the loop body.

And the first answer is correct in the sense that it is not wrong (try it). The poll is simply flawed.


Except it isn't what it does. What that option means is that either the loop is entered, or else if the condition is false from the start it will execute the else block.


It is what it does. It's not all it does, but the option is correct. Only when looking at other options you would notice there is one that is "more" correct. It's not a poll you can draw any conclusions from.

That being said, if you look around, pretty much everyone agrees it is a confusingly implemented feature. I don't think there's any question about that. But at the same time you can find explanations why it is named the way it is, and that can help to remember it better.


I've used the loop -> else construct sparingly - there are times when it is really convenient.

But it is VERY confusing.

Its very confusing, and poor design - IMHO - because the presence of an "else" block, in the typical case, generally indicates that the preceding block was never executed.

When it comes to while/for blocks in python, the keyword literally indicates the opposite - that the preceding block executed without hiccups - and so also execute this block.

Its a great feature - but there should have been another keyword for it: "also" (or something like that)


Hold up. Stop. Read the parent closely.

This is a case where the right answer is only obvious once it's been stated.

> Its a great feature - but there should have been another keyword for it: "also"

SmirkingRevenge - !@#$ing brilliant. You've stepped outside the box and presented an answer that will click as soon as it's read.

Someone make a PEP.


"nobreak" also makes as much sense. Even Raymond Hettinger has suggested that, but getting a new keyword at this point in python is going to be really difficult.


"nobreak" also works, and arguably has less ambiguity than my suggestion.

And I agree getting a new keyword for such a limited use-case feature would be difficult and doesn't feel likely.


Honestly, "else" wasn't confusing to me at all, but "also" would make me very confused.


That may be the nicest reply I've ever had on HN. Thanks!


Not a python programmer (I've used it off and on in a very slashy sort of way).

I'd much prefer syntax like

    da_thing = for ook in blahblah:
       if oook == something:
           break oook

    if da_thing is None:
       print ("Not found")
    else:
       print ('Found', da_thing)


That could be accomplished by doing:

  da_thing = None
  for oook in blahblah:
      if oook == something:
          da_thing = oook
          break

  if da_thing is None:
     print ("Not found")
  else:
     print ('Found', da_thing)
But it does not work if you sometimes want to find `None`, so better would be something like:

  da_thing = sentinel = object()
  for oook in blahblah:
      if oook == something:
          da_thing = oook
          break

  if da_thing is sentinel:
     print ("Not found")
  else:
     print ('Found', da_thing)


This is a common problem in languages which allow the use of "not set" or "not found" indicators as an rvalue, which (sadly) is most of them. Put another way, Python's 'None' or Perl's 'undef' conflate the notion of 'something which has never been set' with the bottom (null) value, aka. the notion of 'the thing here is empty or not useful/usable'. That's a sad conflation to make because it confuses temporal/procedural effects (what has previously been done to this variable?) with contents (does this variable contain a usable value?)

JavaScript takes this to an even more ludicrous place by having both 'null' and 'undefined', putting them close to each other in the truth table, and allowing them both as rvalues.

Does anyone know of any procedural programming languages (or at least languages that encourage mutation, initialization-without-assignment, and rebinding) that have a) a special value for 'never accessed' and b) do not allow that value to be set, only read?


Python's None is exclusively for 'the thing here explicitly doesn't exist' ('the thing here is empty' would be an empty collection and 'something which has never been set' is signified by raising an exception). I think that's the closest you're gonna get to the language you asked for in your last paragraph, because not being able to delete or unset a variable seems to be a serious design flaw to me.


I prefer to extract it to separate function and simply `return` instead of `break`.


The fact that so many commenters here are confused or surprised by what it means or how it works sort of proves to me that it is a bad idea to use this in your code-- especially if anyone else might need to work on that code. I always prefer the clearer, obvious approach, even it is slightly longer.


It is true that this is confusing for people who come across it for the first time, but it is easy to understand/remember once you know this feature.


A counter-intuitive feature is not worth using even if 9/10 python devs know about it. Which they don't seem to. https://twitpic.com/4a52sh

Seems like bad usability.


I agree that many people find it surprising/ bad syntax. I’m one of them, I have to think and double think every time I consider using it. But it’s part of the language, and we should embrace our language that we use.


Oh, that didn't do what I expected at all.

I was expecting this to be similar to:

    if list: for x in list:
       # Some code
    else:
       # else clause
which would have been quite useful to have for general iterators. But apparently it's a statement that triggers unless you use 'break' to end the loop, which just seems like a really weird use case, and smells like a goto.


It is useful when the "normal" flow is to terminate the loop early with a break. For instance, in a linear search algorithm:

    for x in li:
      if x == to_find:
        print("found")
        break
    else:
      print("not found")
Logically, the "else" matches with the "if" introducing the "break" and means "if none of elements cause the break branch to be taken".


Yeah I get that it can be useful, but I just don't like ending a loop in two different ways. It just seems too easy to end up writing something like:

    for x in sorted_list:
        if x == to_find:
            print("found")
            break # we've found a match
        elif to_find > x:
            break # match is not possible
    else:
        print("not found)


Funny, I actually like for-else because it can simplify loop exits.

First of all, the core of a linear search loop always has two exits: the break and the normal (unsuccessful) loop exit.

But in a lookup-or-insert pattern, a regular for loop has two possible states that the system can be in at the end of the loop. With a for-else loop, you can put the or-insert part into the else block, so that there is a simpler invariant: after the loop construct, the element always exists and has been found.

I was honestly surprised by all the negativity in this thread. To offer an alternative opinion, there have been plenty of times when I missed having for-else while writing C.


I feel the same way. For-else is one of those things that make me miss Python when I'm working in another language, alongside things like list/set/dict comprehensions, Python's object model and it's way of dealing with generators and iteration.


") # There you go

Also, nice counterexample.


It's a special case that matches `else` with `if` at the wrong level of indentation. Seems a lot like faux-simplicity, like a lot of Python: it seems simple and "pythonic" superficially, but really forces the coder to learn special cases.


The way I explained how "else" pairs with "if" in preceding inner block is more of a mnemonic device than anything; "logically" was probably the wrong word.

Python is probably not as simple as some like to claim, but this is not really any kind of special case. To have a special case, you need to have a normal-case behavior that is then broken: for instance, a function f(n) that "computes the factorial of n, unless if n is 1 in which case it returns 42" contains a special case, because n = 1 used to have a normal-case behavior of f(1) = 1. Special cases require the user to learn of its existence, or they will be bitten by it.

In this case, there isn't any special-casing: the support for "else" after a "for" block doesn't have any predetermined "normal-case" behavior. In most other languages, such a construct is simply not supported. Python adds a new behavior here without breaking any existing behavior.


Here the "normal case" I was referring to was how "else" usually appears at the same level as a predicate block.


> which just seems like a really weird use case

I think quite the opposite: What you expected seems unnecessary to me because that is already easy to do with that if-statement in your example.

On the other hand, to accomplish this "for-else" you would have to do something like this:

    found = false
    for x in list:
        if ...:
           found = true
    if not found:
       ...
While writing this example I noticed that I also could make good use of the negativ: Something like

    for x in list:
        ...
    then:
        ...


To achieve the for else I suggested for general generators requires a similar pattern to your suggestion so both of them save equal amounts of complexity.

However I'm struggling to find a use case for the python for-else definition where you don't end up doing something like 'result=something; break' anyway. What would be the point of breaking if you don't have any results? And if you have such a situation, wouldn't an exception make more sense?

Conversely to check if an iterator is non-empty you need to set a boolean to true, and you can't easily skip this part so you end up setting it to true every loop. It would have been quite nice to have an 'else' condition that triggers when the generator is empty (which also fits quite nicely with all empty containers being considered falsy).


> However I'm struggling to find a use case for the python for-else definition where you don't end up doing something like 'result=something; break' anyway. What would be the point of breaking if you don't have any results? And if you have such a situation, wouldn't an exception make more sense?

This is also useful for an accumulator and sentinel case. Say you're parsing a file and need to grab a null-terminated string:

  result = ''
  for b in file_bytes_iterator:
    if b == '\0': break
    result += b
  else:
    throw ValueError("String is unterminated")
result will contain the parsed string, and file_bytes_iterator will be ready to return the first byte after the terminator, to continue parsing the file


For what it's worth you could also write that as:

    result = ''
    for b in file_bytes_iterator:
      if b == '\0': break
      result += b
    if b != '\0':
      throw ValueError("String is unterminated")
And while we're one the subject, I would also really like to have a construction like:

    result = ''
    while b != '\0' for b in file_bytes_iterator:
      result += b
    if b != '\0':
      throw ValueError("String is unterminated")
but I fear opinion might be divided on that one.


It's a syntax sugar feature, so its presence or absence will make little difference in the grand scheme of things -- there will always be a relatively easy way to rewrite code without it.

Personally, I would prefer the loop iteration variable to only be in scope within the loop body, and generally write code as if that were true. Also, repeating the sentinel test is asking for a future edit to change one and miss the other.


Your suggested replacement duplicates the break condition, which is not ideal for maintainability.

Also, it only happens to work due to Python's variable scoping rules. I have wished for a for-else construct in C and C++ many times: having to broaden the scope of a loop iteration variable just to duplicate a loop break check really sucks.


After letting it steep overnight, I came up with this variant which addresses most of the issues that have come up in this thread:

  try:
    result = ''
    while (b = next(file_bytes_iterator)) != '\0':
      result += b
    # maybe parse more of the file here
  except StopIteration:
    throw ValueError("Unexpected end of file")


> and smells like a goto.

What about while/break, for/break, try/except, etc doesn't "smell like a goto"? Isn't that the whole point of these sophisticated constructs, to serve as more rigorous replacements for "goto"?


Sure, and break/continue etc. should be discouraged for that precise reason. I don't quite see try/except/finally as a goto, since I'm not sure how to make one that behaves that way.

However IMO the best 'rigorous' model of programs is maintaining pre and post conditions. Escaping early when the post-condition is satisfied is compatible with this model (like with e.g. return/break/continue). Escaping in two different ways is not.

If you really need to handle exceptional cases where you need to escape a loop differently then exceptions are the better option IMHO, these also have a fairly rigorous definition in terms of Monads. Obviously if actual Monads (which in the case of python would be containers / generators) are an option they should be preferred.


> Sure, and break/continue etc. should be discouraged for that precise reason.

If you naively banish break and continue, but then the programmer has to work around your banishment by adding one more state variable, and assigning to that variable, you have not gained anything. You've pushed down a bulge in the carpet, only to have it pop up elsewhere.

Assignment and goto are bedfellows, which is why purely functional programming banishes both. (Goto, at its essence, is in fact an assignment to the program counter.)


Generally speaking, things that use break can be cleaned up by using python's `any` or `all` constructs over a slightly reworded loop. This isn't always true, but handles most of the common cases.

I don't think that I've yet come across a use of break/continue that can't be pretty cleanly refactored into a helper method like

    def my_filter(items):
        for x in items:
            if cond(x):
                yield x

    for x in my_filter(base_list):
        process(x)
This ends up being pretty clean and describes exactly what/why you're filtering things, plus as the conditions change you can test things individually.


One might also consider using something like `map(process, filter(cond, items))`


Note I said discouraged not banished.


> However IMO the best 'rigorous' model of programs is maintaining pre and post conditions. Escaping early when the post-condition is satisfied is compatible with this model (like with e.g. return/break/continue). Escaping in two different ways is not.

Escaping in two different ways really isn't a problem for that kind of analysis. You just have to make sure that the post-condition is satisfied at each exit. For/else is actually a great construct for that, where the else branch is used to achieve the same post-condition as after a break, which would be more difficult if there were no difference between the loop exiting with a break or by exhausting the iterator.


> I don't quite see try/except/finally as a goto, since I'm not sure how to make one that behaves that way.

I suggested try/except because goto is very commonly used to provide similar behaviour in C where there are no exceptions.

> break/continue etc. should be discouraged for that precise reason ... the best 'rigorous' model of programs is maintaining pre and post conditions ... Obviously if actual Monads are an option they should be preferred.

But why? Do you have any evidence that this leads to better/more robust/easier to understand code in practice?


Does personal experience count? (note also that I indicated that ultimately all of this is just my opinion). Personally I find I mostly end up creating bugs when I don't have a clear enough image of what the proper pre-condition to a loop should be and what its post-condition should be. I therefore believe that code that by its very structure can't easily be assigned pre/post conditions would make things quite difficult. Post-conditions also tend to make it easier to reason that the code is correct (provided they are documented).

As for the Monad quote, well it's use is somewhat limited in Python, however returning a 'trivial' result rather than an exception usually ends up being easier to work with. Which is appropriate varies though, and in Python there's not much reason to avoid exceptions for performance reasons anyway.


which just seems like a really weird use case, and smells like a goto.

That's exactly what it's for. In C, this is a pretty common pattern:

      for( ... )
        if( ... )
          goto found;
      /* else not found */
      ...
    found:
      ...
I remember when I had to use a bit of Python and lamented its lack of goto, then came across this "else loop" and thought "yes, they even thought of that."


This is usually quite useful, actually! It happens a lot in numerical computing where you try a bunch of possible solutions and if none are found then you output some sort of "infeasibility" result, otherwise you output the result you broke at (skipping the else statement).


Putting the loop in its own function would solve the problem as well (allowing you to just return the output), or if that's not an option then you need to store the output anyway so you might as well just add a check 'if output is None:'.


I hate adding syntax but you know what's really useful? Django's `for .. empty` template syntax.

I would like to see this in Python:

  for i in []:
    print(i)
  empty:
    print("Sequence was empty!")


Write up a PEP! It's a great idea. Syntax can be good if it makes sense and makes code easier to read and write.


Won't be accepted. Recognized as a problem, but now set in stone.


Maybe in python 4...


Nope, been discussed many times on the lists, neither planned.


Except the python "for/else" executes regardless of whether there's a sequence. I've used it before for retry logic, but it confuses the heck out of people.


I think the user you're replying to is just saying that a for...empty would be nice too.


I've been using this for years and never thought twice about it, I'm shocked by all the negativity.

It's great for controlling iteration in a nested loop:

  for item in report():
      for event in in item['events']:
          if event in events_we_care_about:
              break
      else:
          continue
      act_upon(item)
I realize it's a trivial example that could be written without a nested loop, but there are often good reasons for nested loops and littering them with control variables and if statements gets messy quick. I have always found for-else to produce more elegant code.

Your criticisms may be quite valid, I just wanted to share my surprise.


I may be missing something... but how is that clearer than

  for item in report():
      for event in item['events']:
          if event in events_we_care_about:
              act_upon(event)
              break


It's a trivial example. Just to demonstrate how for else helps to control an outer loop from within an inner loop.


Your act_upon is passed values from the inner loop. GP's act_upon is passed values from the outer loop.


I would write that as

    for item in report():
        if any(event in events_we_care_about
               for event in item['events']):
            act_upon(item)
But obviously the features that enable this form didn't exist when for...else was added to the language.


Clearly it was a poor example, I wouldn't structure simple code this way either but in more complex situations it can be the simplest solution.


To me, the else isn’t intuitive if you haven’t encountered it before. It’s a nice feature, but perhaps better naming could improve code readability.

How about for-finally? It’s a hard thing to name correctly, but to me this seems to be slightly more intuitive if you’ve never seen the feature before.


You can’t use "finally" because that implies an expression that runs regardless, which this does not (it only runs if the loop never breaks).


Yeah, that’s a good point. To me it’s a little more clear than ‘else’ but there might be a better name still that doesn’t suggest this code always runs. Naming is hard :)


Perhaps `for...lastly...else`? `lastly` being the current meaning of `else` (something that happens after the iteration has completed), `else` being the thing I wish it meant (something that happens when the iterator was empty).


Finally would suggest it's always executed, even if aborted, like try...finally


I had my first legitimate use of for else last week after knowing about it for years and it (the keyword) never making much sense.

I had to call an API for it to load data into a cache, but sometimes this wouldn't work so I made it try up to 3 times, otherwise it would show an error. So something like:

    for i in range(3):
        if load_cache():
            log("success")
            break
    else:
        log("failure")
Else made total sense here.


Nearly everybody seems surprised by what it does, which would appear to constitute a fairly strong violation of the "principle of least surprise" that is one of the most cited guiding principles employed by the designers of Python. As such it would seem better to be described as an "anti-tip" or something like that.


It was a feature van Rossum added in the earliest days of Python, on 14 Oct 1990, back before he fully developed his design sense.

As I recall, he said added it because the implementation structure of the "for" code matched the implementation structure of the "if" code, and he felt that 'for' could use an 'else'.


It is worth noting that the else clause can also be used with while loops.

[1]: https://docs.python.org/3.6/reference/compound_stmts.html#wh...


And the full form of a Python exception handling block is not try/except/finally. It's try/except/else/finally.

The 'try' block contains code which might raise an exception.

It can be followed by zero or more 'except' blocks, each of which indicates the type(s) of exception it will catch and contains code to execute in the event of that type of exception.

The 'except' block(s) can be followed by one 'else' block, which contains code to execute in the event no exception was raised by the code in the 'try' block.

And then there is optionally a 'finally' block, containing code that must be executed in all circumstances.


Seems a bit useless since

    while x < 2:
        ...
    else: 
        print('>= 2')
is the same as

    while x < 2:
        ... 
    print('>= 2')
unless you use a break. Just seems unnecessarily confusing to me. I have used for ... else though.


I think that the subtle difference is that in the second example "print('>= 2')" is always executed whereas in the first example the print statements is executed only if no break was executed in the while loop.

Quoting documentation: "A break statement executed in the first suite terminates the loop without executing the else clause’s suite."


I did say "unless you use a break". I don't think this is very useful.


But... for+else without break can be rewritten the same way. And yet you claim that you've used for+else.

But indeed, in my experience, while+else are much less common that for+else. I'm not sure why.


While this is a great capability to have, the tendency to have an indented “if” preceding a not-indented “else” just always seems like somebody might have made a mistake.

I would rather something that captures the loop state itself as an operable object, such as:

    with for as loop:
        for x in my_list:
            ...
        if not loop.broken:
            ...


Interesting how people's intuitions differ. For me hiding yet another . method that requires discovery is more burden on the human. I'd rather reach for map/filter => processing.


The if you're talking about ends in a break.

To me and many others, having an else associated to an if that ends in break/continue/return is a code smell, because it misleadingly suggests that there are two possible paths of control flow that reach the code after the if/else, when in reality you can only reach that point through the else block.


Seconded


One thing that for-else is very useful for is implementing an elegant and easy-to-understand retry mechanism:

  for _ in range(3):
      success = do_something()
      if success:
          break
  else:
      raise FailedToDoSomething
I generally like this feature of the for loop and it would be a shame if it was removed.


Indeed I have use this idiom a lot. The alternative is to store a state variable indicating success which isn't very nice.


I find this pattern very much better expressed with functional programming:

  item = next((item for item in container if search_something(item)), None)
  if item:
      process(item)
  else:
      not_found_in_container()
instead of as suggested in the blog post:

  for item in container:
      if search_something(item):
          # Found it!
          process(item)
          break
  else:
      # Didn't find anything..
      not_found_in_container()


Your suggestion contains bugs, though. To make it fully equivalent to the code in the blog post, it would need to be:

  sentinel = object()
  item = next((item for item in container if search_something(item)), sentinel)
  if item is not sentinel:
      process(item)
  else:
      not_found_in_container()
... because item could be None or another falsy value.


Since Python doesn't have goto statements, this removes one of the situations where a goto statement is the best tool to use in a language like C++.


In a sense, raise/except is a goto.

Go-lang decided against exceptions in part because they are equivalent to gotos.


But Go does have a goto statement...?


And like many languages that do, it's an extreme code smell to use it, whereas most(if not all?) languages that have exceptions recommend to use them. Though technically, Go does have exceptions (under the name "panic"), just not a very easy to use "try/catch" block (the "try/catch" stuff is function level, so you have to do anonymous functions to "emulate" other language's try/catch). Also, Go's goto has restrictions.


It's not really a code smell. They just don't let newbies use it in CS 101 because they want them to think in a structured manner about loops and such.


This is strongly advised against in the book Effective Python as the semantics are opposite to what one is used to. The else clause will always be executed unless there's a 'break' in the for-loop.


Now I feel like I have to rewrite every single Python script I ever wrote.


Please don't bother. Obscure, very rarely used language constructs like these often don't really enhance the code's maintainability. On the contrary; would you have understood the very same code yourself before reading this article if someone else had used the construct?


I think if you read it, you'd read it and get it.


not really a good idea


Well, that’s a dumb feature. Very counterintuitive, requires you to stop and think, and there’s a high probability that it’ll be misunderstood at first glance. I hate things like this - you’ll read it, think you understand it, experience a bug, spend hours debugging, then finally look up the documentation for ‘else’ and see that it means something different from what you thought.


I write quite often something like that:

    for i in range(10):
      for j in range(10):
        if condition(i,j):
          dosomething
          break
      else:
        continue
      break

It's a pattern that ensure that dosometing is run at most once by "propagating" the break to the outer loop.

The alternative is use a function and do a return.

Any though of this ?


I'm not a python programmer but I find the last 4 lines rather hard to read. Unless there is a performance reason to not do so, I'd argue that you are iterating over pairs (i,j) here and I'd make the code clear. There are 100 pairs, and once you find one that satisfies the condition you are done. Again, not a python programmer but it might look like

  (x, y) for x in range(10) for y in range(10)
     if condition(i,j)
        dosomething;
        break;
Edit, ("zip" is not the outer product... )


The relevant function is itertools.product() [1]. It can give the Cartesian product of arbitrary iterables, but if you have the same iterable more than once, as in this example, you can take advantage of the repeat= optional parameter to simplify the call a little bit:

    for x, y in itertools.product(range(10), repeat=2):
        if condition(x, y):
            dosomething(x, y)
            break
Alternatively, you can use Python list comprehensions:

    prodlist = [x, y for x in range(10) for y in range(10)]
    for x, y in prodlist:
        if condition(x, y):
            dosomething(x, y)
            break
Overall, I still prefer refactoring into a function and using return, as the GP comment suggested.

[1] https://docs.python.org/3/library/itertools.html#itertools.p...


I'm in favour of writing simple python that even a junior dev can read rather than using niche syntax tricks. Guess that depends on whether you think a junior should know this syntax.


This is a weird response. You can definitely argue that it's bad syntactic design because people seem to expect it to do something different, but if the argument is just that some category of programmer doesn't already know what it does, you're pretty much arguing against introducing any new language feature, syntax or library that wasn't taught in your imagined CS101 or whatever.


> you're pretty much arguing against introducing any new language feature, syntax or library that wasn't taught in your imagined CS101 or whatever.

But there clearly is a body of knowledge which you can discover yourself: you can interview candidates, read discussions on SO, read library code, and you will find some techniques are commonly used, and some are rarely used.

That distribution of what's known is changing over time regardless of what we do, because our peers are constantly learning and studying at different rates.

This feature has been around for years, and is so rarely used that only language mavens know about it. Thus, you can observe that this is a failed feature and avoid using it.

We can always reevaluate if people understand it later on, since that could change.


On the other hand, when you do run across this construct in the wild, it is trivial to look up what it does and learn. I imagine that's how most people learned about it, since it doesn't exist in other languages.

In a way I find your comment contradictory: on the one hand, you appreciate and embrace that the body of common knowledge can change. On the other hand, you use the current state of common knowledge as an argument against doing things that can improve the common knowledge.


As a counterpoint, a similar syntactic construct exists in several templating languages, and behaves radically different...


I agree with your point, but I meant that I haven't seen this specific syntax in many py tutorials or projects I've read.


This has long (always?) been a basic part of the Python language. How do you decide what is a “niche syntax trick”?

Here’s the python language reference from python version 1.4 (1996), http://web.archive.org/web/19970606191447/http://www.python....

> The expression list is evaluated once; it should yield a sequence. The suite is then executed once for each item in the sequence, in the order of ascending indices. Each item in turn is assigned to the target list using the standard rules for assignments, and then the suite is executed. When the items are exhausted (which is immediately when the sequence is empty), the suite in the else clause, if present, is executed, and the loop terminates.

> A break statement executed in the first suite terminates the loop without executing the else clause's suite. A continue statement executed in the first suite skips the rest of the suite and continues with the next item, or with the else clause if there was no next item.

* * *

Here’s the tutorial, http://web.archive.org/web/19970606183615/http://www.python....

> The break statement, like in C, breaks out of the smallest enclosing for or while loop.

> The continue statement, also borrowed from C, continues with the next iteration of the loop.

> Loop statements may have an else clause; it is executed when the loop terminates through exhaustion of the list (with for) or when the condition becomes false (with while), but not when the loop is terminated by a break statement. [... and then an example ...]


> How do you decide what is a “niche syntax trick”?

You decide with experience. When other people read my code, this is probably the feature that surprises them most. Other features that they haven't seen before usually make sense from context. This one is surprising enough that I often forget whether it executes if there is a break or if there isn't a break.

When explaining and remembering what the syntax means dwarfs the effort saved by using the syntax, it's a niche syntax trick.


My point was more towards benwilber0's comment; not a lot of usage in code I've seen.


I can't see this ever being used. As a non-python programmer, if I ran across this it would be very confusing. If it's simple enough to write the same code without the else block and have your code be more readable, why not do that?


I much more frequently wish that I could express 'or do this if zero iterations' than I use for-else as it is; I do so wish it had the former semantic.

That and the absence of do-while are the cause of a good deal of non-DRY python around loops.


The `else` clause follows the same general pattern wherever it supported: `for`, `while` and `try` are consistent. It usually makes code shorter or at least leads to narrowing concerns when used with `try`.

For some reason I often forget to use it though.


I would never include this in my code. It’s a construct most devs have never seen in Python and it differs from anything I’ve seen in other languages.

Always KISS.


I'm curious why people are interested in this. No way I would have predicted it would get to the top of HN.


Or maybe it was a terrible "or else" pun that does not deserve much discussion.


Else is a bit of a confusing name for the keyword IMO.

How about "ifcompleted"


Pretty convenient feature.


I hate it when code uses weird niche features that are not obvious, are impossible to look up and add 0 value aside from saving a couple of lines of code.


> are impossible to look up

Questions about Python's syntax are anything but impossible to look up. Python has excellent documentation around this, and the steps to get to the right part of the documentation are pretty straight-forward:

1. Start at the main page, https://docs.python.org/

2. Click "Language Reference"; note the subtitle that clearly hints this is what we want: "describes syntax and language elements"

3. We are interested in for loops, so we click "8.3. The for statement"; the resulting page describes the behavior.


Agreed. The way this ends up looking is absolutely unclear what it does - and seems like more a quirk then a feature.


Me too, but this use case seems clearer than most features I'd label that way, even though I didn't know about it either.

It certainly shouldn't be used when unclear, just like any feature. There's no reason this feature's sensible use cases should be as niche as they currently are.


[Mistake, a moderator please delete this comment! Completely agree with the parent comment now. for...else is dumb and unintuitive. I misunderstood its function, which proves the above point to me.]


> The symantics of for...else are immediately obvious

The code in your example is how it reads, but that's not how it works. The "obvious" reading is wrong, so it's obviously not "immediately obvious".

This is what it's actually doing:

  found = False
  for l in get_list():
    if ...:
      found = True
      break
  if not found:
    ...


I think it's worth keeping the comment around for that exact reason. It underscores that the concept really is confusing and mislabeled.


This is coincidentally my highest rated Stackoverflow answer ever. :)

https://stackoverflow.com/questions/9979970/why-does-python-...

Tl;dr my highly opinionated opinion is that one should never use the construct


For-else is nothing. The really weird one is the Try-else.

The For/While-else does exactly what I expect. I'm not sure why others find it confusing.

The Try-else does make some code more readable but is quite rare I would say.


Well, for one thing, a few templating engines have a "for each item, print x, else if there are no items, print y". (for instance: https://handlebarsjs.com/builtin_helpers.html#iteration)


That's not intuitive at all. That's equivalent to:

    if len(items) == 0:
        print(y)
    else:
        for item in items:
            print(item)
Which means there's a whole extra implicit if. The for in Python is really this:

    it = iter(items)
    while True:
        try:
            item = next(it)
        except StopIteration:
            print("else")
            break
        print(item)
Where that try/except is like "if there is a next item do this, else do this", hence:

    for item in items:
        print(item)
    else:
        print("else")


Never said intuitive, but that construct is EXTREMELY useful for things like "no results found" and such, when a list of items is empty. Therefore, it is usually explained quite early and used often in template languages that have it. This is the first time I've seen python's, after dealing with several large codebases written in python.


Never use this in the real world please.


This is discussed as item 12 in Effective Python. The author argues that you should avoid "else" blocks after "for" and "while" loops. The main argument is that the behavior is not intuitive and could be confusing upon reading given its somewhat obscure usage.

He suggests using a helper function with an early exit or setting a result variable and using a break in your loop instead.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: