I'd like a different name for for "for's" else.
I've been using method/function redefinition in place of conditionals related to initialization.
In a for loop, the else clause only runs if the loop successfully completes (isn't broken), so the else in for-else means the total opposite of what it means in every other pattern.
I think it would make a lot more sense if it were replaced with "done" or "upon" or something else that communicates how it works.
Edit: This is wrong. See replies.
However, that's exactly how it doesn't work and that's my point. The else clause is only ran if the loop DOESN'T break.
>When used with a loop, the else clause has more in common with the else clause of a try statement than it does with that of if statements: a try statement’s else clause runs when no exception occurs, and a loop’s else clause runs when no break occurs.
Inside any loop are a conditional and a jump. In pseudocode:
if not <loop end condition> then
<do loop things>
<jump to top of loop>
else //loop is done
<do things in the case that the loop has terminated naturally>
If we 'break' in the body of the loop for some reason, we will never hit the 'else' in this chunk of code. As Mr. Hettinger explains, this is obvious to anyone reading Knuth or coming from a 'goto' style of control flow. This is not an insult, but an observation. (Un)Fortunately, structured programming is the absolute norm now, and we learn looping constructs directly, rather than learning 'goto' and then building to looping constructs. Especially in a language with rich iteration protocols, such as Python, it is very much unapparent that the looping constructs are fancy wrappers around 'goto'.Link to the talk: https://youtu.be/OSGv2VnC0go?t=948
I think the idea is cool (albeit rarely useful), but a different word than "else" for the same functionality would go a long way.
Generator return, on the other hand, is something I didn’t even know about (basically, return X in a generator raises StopIteration(X)). I don’t think it’s super useful because even inspecting the arguments to an exception tends to be uncommon practice in most code I’ve seen (more frequently, you just want to report or suppress the error depending on its type). I’m sure there’s a niche use for it somewhere though!
for ...:
...
else:
# loop ended, no positions found
return ... for … :
…
else: # break didn’t occur
…
I typically make it even shorter, but this is probably the most readable. def my_generator():
yield 1
yield 2
return 3
If you use that generator in a for loop then it will only put 1 and 2 into the iterator variable. You have to use the generator in a more direct way to get access to the 3.I haven't made use of return values from generators in my code either, but I believe they're used under the hood in coroutines in async code.
def lex(src):
state = skip_whitespace
while state is not None:
state = yield from state()
It wasn't particularly essential to use the return value from the generator here, as I could have just made the state variable available in the scope of the state functions for them to mutate, but this seemed like a cleaner way to do it as it enforced the idea that each new state corresponded to a new function.It's actually a super useful pattern for any kind of search iteration. I've used it many times.
for i in range(n):
if search(i) == val:
break
else:
raise KeyError("not found")
found_index = i
It reduces the need for an additional flag. More importantly it makes it easier to ensure that the break condition is satisfied such that the loop variable can be used properly later on. Personally, I think an else condition should almost always be there for loops that gets broken early, similarly to always finishing if else chain with a final else.ETA: Another pattern where it's really useful is to replace
while True:
with a safer guaranteed terminating loop, for i in range(MAX_ITER):
...
else:
raise RuntimeError("exceeded max iterations") for i in range(n):
if search(i) == val:
break
raise KeyError("not found")
found_index = iThe point of the for / else is that the else only gets evaluated when the for terminates without a break. So in the example you only get a KeyError if the search() never returns val.
Part of the confusion I guess is that the else: in my example is paired with for, not with if, Python indentation being significant etc.
Before being introduced to `for`/`else`, I'd have written the example you gave as:
result = None
for i in range(n):
if search(i) == val:
result = i
if not result:
raise KeyError("not found")I used it recently in some mutating code in which I wanted to make a change, and also know if it did actually make a change. If the routine gets to the end of the loop without finding a place to make a change, it hits the "else" and returns False.
I don't think I've seen it in the wild either. Perhaps too new?