While in theory this is useful, it's one of the least intuitive parts of python for me. I avoid them whenever possible because I feel "else" conveys the intent very poorly. (And I've been writing python for years.)
I agree. Intuitively, I would expect for item in list: .... else: ... to execute the "else" clause only if the list was empty and hence the for loop didn't iterate at all.
How about a keyword such as "after"? I feel like that could have extensible potential in other areas of the language, and the name makes a little sense logically speaking.
What's more, it dances very close to something that is a super-common error for novice programmers: the early return from a loop. I grade AP exams every year, and one of the hands-down most common conceptual mistakes I've seen (on problems where this is relevant) goes something like this:
boolean lookForSomething(int parameter) {
for (Item item: list) {
if (item.matches(parameter))
return true;
else
return false;
}
}
where a correct answer would omit the "else" and put the "return false;" outside the bracketed loop (or, keep a boolean variable updated and then return that after the loop is done. Let's translate that to python:
def lookForSomething(parameter):
for item in list:
if matches(item, parameter):
return true
else:
return false
As a conceptual matter, for a beginner who is still trying to nail down the whole notion of "can stop early when found, but have to scan the whole list if not found", it is just plain nasty that the following code is not only correct but idiomatic:
def lookForSomething(parameter):
for item in list:
if matches(item, parameter):
return true
else:
return false
Remember that I was talking about novice programmers, here. I'll concede that "idiomatic" was a bit strong (and the True/False thing was a brain fart), but I think my main point stands: encouraging the use of an "else:" that can attach to loops, with an unobvious and somewhat nuanced semantics, is not kind to newcomers. And the fact remains that the difference between a correct implementation and one that is wrong in exactly the way that a lot of newcomers get it wrong is nothing but indentation.
I totally agree. I wrote Python code professionally for five years and am shocked to find I was wrong about this all this time. I must have introduced quite a few bugs. :-/
What finally made it intuitive for me: assuming the loop contains an if condition: break, you can consider the else-clause to be the else of the if statement within the loop.
I think the real issue is that the word 'else' is ambiguous in the context. Everyone comes in with a different idea of what the 'else' is a fallback clause to. Guido obviously picked one particular case, but there are other valid meanings.
For example, I think that it's more common to want to do something like:
# mnemonic:
# for thing in list_of_things DO_SOMETHING else DO_SOMETHING_ELSE
if list_of_things:
for thing in list_of_things:
print thing
else:
print "No things!"
Than:
for thing in list_of_things:
if thing.is_awesome:
break
else:
print "No awesome things!"
And what about conditional code that you want to execute when a loop does break early?
error_found = False
for thing in list_of_things:
if thing.error_condition:
error_found = True
break
if error_found:
pass
I'm not sure how the indentation rules come into play here. I'm not saying that this:
if blah:
for .. in ..:
..
else:
..
is visually confused with:
for .. in ..:
..
else:
..
What I'm saying is that for-else and while-else add syntax to the language that only solves a single specific instance of a class of similar issues, making it confusing.
Let's consider the issues that are in the same class:
- execute code block when loop condition isn't met the first time (i.e. loop never executes).
- execute code block when loop exits prematurely (e.g. break).
- execute code block when loop doesn't exit prematurely (e.g. no break) // execute code block when loop condition evals to false (first eval or any subsequent eval)
Only one of these issues is solved by the for-else/while-else syntax currently in Python, and using a generic keyword (else) just heightens the confusion (especially since this syntax differs from many other languages).
All of these cases may require the use of sentinels / additional checks to implement. Why is one specific instance any more relevant than the others to the point that it gets special treatment (i.e. special syntax in the core language)?
That logic definitely helps me think about it. But it does seem like a switch from the other wording that python uses. Feels like finally would be more intuitive, whereas without thinking about the search loop case else feels like something that would be triggered by a break or return. I wonder if using the feature would be more common if it was more intuitively worded.
Glad you posted this. I came to the comments to post the same thing[1] because thinking of it as "notfound" makes remembering (and comprehending it) easier. :)
Not only it is not very intuitive, I also find it to hurt the linearity of code. The code which will be executed after the loop ends depends on how the loop ended. break statement is usually a glorified but mostly harmless go to, but I think Python's go to statement isn't so harmless as it adds complexity but doesn't have big benefits.
But, I do think that the else clause on try\except blocks is less harmful. It is less harmful because the control flow complexity of exceptions handling is already there on the catch clause, so while exceptions does add big control flow changes, the addition of the else clause causes a relative minor change on the linearity of code (as it is already broken by catch: clause.)
There will always be non-expert programmers of many languages. Avoiding language-specific constructs will not solve the issue.
As far as portability is concerned (if you port software by hand a lot, for example), this is indeed an issue; but if you work with people who write python code, you might as well use everything in your toolbox.
> There will always be non-expert programmers of many languages. Avoiding language-specific constructs will not solve the issue.
This is a language-specific issue that occurs rarely in the wild. Yes, it's part of the language, but it's used rarely enough that it can trip up even experienced Python programmers.
For example, Perl (until more recently) allowed one to change arrays to be 1-indexed, rather than 0-indexed at runtime. Just because it's part of the language doesn't mean that it's a good thing to use it.
After 2 hours of searching to find the appropriate piece of documentation.
I encountered this situation. I assumed the else belonged to an if, but the indentation rules were somehow being broken. What would you expect me to look up in the docs?
For me, the optional else on for loops isn't nearly as mentally destabilizing as the optional else on try-except blocks. An ex-employee used to love doing that:
try:
None.doit()
except Exception as e:
logger.exception('Nope')
else:
# now what?
pass
Also known as "Dijkstra's loop" to give credit where credit is due. Only other language I know that has it though is Oberon (and that just introduced it in a pretty recent update)