for i in l:
print(i)
then:
print("iteration finished without breaking") for i in l:
print(i)
then:
print("iteration finished without breaking")> 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...
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.
Just because it isn't named in a way people find intuitive doesn't mean it isn't useful.
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.
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? try:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy)
except ImportError:
pass
else:
print("well that didn't work") $ 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. $ 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.print("well that's done")
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++.
However as far as programming is concerned, many things have been deprecated through transpilers and linters (for example "==" throws a warning in ESlint).
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.
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.
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++.
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.
"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."
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.
Running that on my personal projects folder gives me the following result:
for elses 60
for loops 1815
# modules 598
# skipped 2 for elses 0
for loops 428
# modules 673
# skipped 26
for elses 99
for loops 6058
# modules 2016
# skipped 286This 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.
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.
Deprecating any feature without any strong reason makes me irritateted and I don't find any strong reason for doing so.
'for-else' never needs to be removed but the code cleanup will happen over time.
Sounds like if people were not already coding C, C++ and Pascal when Python was invented...
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... >
...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.
Not saying is absolves the for/while else syntax, but it’s still using the expected iteration constructs.
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.
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.
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.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.
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.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).
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.
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.
(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.)
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-...
{{#each paragraphs}}
<p>{{this}}</p>
{{else}}
<p class="empty">No content</p>
{{/each}}
--- https://handlebarsjs.com/builtin_helpers.htmlSuppose 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."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.
--------
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 :)
> When the condition is false, it breaks.
And then you said:
> If the for loop exits normally, the condition was false.
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.
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.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. for item in list:
if search_condition:
found_item=item
break
else:
found_item=default_itemfound_item = next((item for item in list if search_condition), default_item)
that explains exactly what it does