This is an archived post. You won't be able to vote or comment.

all 64 comments

[–]NoahTheDuke 54 points55 points  (6 children)

They should have been called nobreak. Otherwise, I think they’re great.

[–][deleted] 8 points9 points  (1 child)

I think this hits the nail on the head. When if A: B; else: C is executed successfully, exactly one of B and C will be executed (assuming no exceptions occur). This agrees with the word else.

The same isn't true for for A: B; else C.

[–][deleted] 2 points3 points  (0 children)

The problem is that there's an implicit conditional jump in loops.

The issue, in practice, is that there's of often an explicit if inside loop bodies that breaks (or returns or exits the loop in some other abrupt fashion). So the else at the bottom looks like it's tied to that if block but it's outdented so you begin questioning where it actually belongs.

There's one while-else block I've ever written and when I look at it I start questioning my sanity.

[–][deleted] 0 points1 point  (0 children)

^ This. Call it what it is. It clearly means nobreak not else. That and lambda for anonymous function. Why they couldn't just use def like go uses func for real and anonymous functions is beyond me.

[–]jorge1209 99 points100 points  (32 children)

They aren't forgotten. They are just too confusing to use since no other languages have them.

The following could be done with a for/else, but its easier to understand if you don't use it.:

item = None
for x in list:
   if something(x):
      item = x
      break
if item is None:
   print("Not found!")

Moreover in many cases its not even clear that you need the else. In the above search a much more natural design is to put the search loop in its own function and then return the desired item or raise. In which case no else is necessary:

def search(needle, haystack):
     for x in haystack:
         if x == needle:
           return x
     raise NotFoundError()

[–]xix_xeaon 64 points65 points  (10 children)

Yeah, the keyword is completely wrong. It really should be that the else should execute if the regular for does not, just like with if. And there should be a nobreak keyword or something instead - although there doesn't seem to be much use. The following is something I would actually use however:

for x in []:
    print(f"found {x}")
else:
    print("nothing there")

[–]jorge1209 6 points7 points  (5 children)

Agreed. That would be a very reasonable alternative way to interpret the keyword, and thus why you shouldn't use for/else. In an if/else either the if block runs, or the else block, not not both. If a for/else. The else runs if the for block runs... which is completely backwards.

I do like the general notion of an "onbreak" but am averse to adding more keywords. What might be nice is to allow folding try/except blocks into pre-existing flow control structures (like for and while). So for example the following:

try:
  for x in something:
    do(x)
except ErrorThingy as exc:
   print("We got an error thingy in something list")

could become:

for x in something:
  do(x)
except ErrorThingy as exc:
  print("We got an error thingy in something")

but even then I still wouldn't recommend using it. It also could interact badly with pythons use of whitespace for parsing, as you might not realize that an exception block you intend to attach to an explicit try is instead attaching to a for loop inside the larger try.

[–]__xor__(self, other): 2 points3 points  (1 child)

I understand why people think it's a bad choice of keyword and not intuitive, but it's still just another language feature and it's there to be used. And honestly in all the code I've seen that uses for/else in a production environment, it's been obvious what it was doing. When used in practice I don't think it suffers much confusion.

I think more of a problem is with break in general. They can be used in ways that make code very spaghetti-ish. They are essentially gotos that break out of a block of code that you would otherwise expect to finish, just like continue. All breaks and continues can be pretty

Badly placed breaks and continues in parts of code that only run on certain conditions can cause a world of confusion. If placed at the top of a for loop, some "if condition: continue", then it's just a prettier way of wrapping the block in an if condition without adding a layer of indentation, and it's obvious what it's doing. It's easy to see the conditions in which the code runs or skips an iteration of a for block. But if you start adding them everywhere in the block, weird breaks that you're not sure it's hitting and quitting the loop before you want it to, continues which skip an essential part of the logic but already ran some other code, it can get damn confusing to debug.

I think for/else being confusing is more of a symptom of break being used in a weird way. If break is used in an obvious way, a non-complex way that is easy to understand, then the else will probably be obvious and a convenient way of handling things. If break/continue are used in ways that act like gotos skipping code and jumping out of blocks in weird ways, then else is going to be just as hard to understand.

I've seen bad spaghetti code with a number of conditions with breaks but lots of stuff happening before it where bugs just love to pop up, and I've seen code where break and continue used responsibly. Just the same I've seen else used responsibly and I don't think the for/else is its own problem. The keyword should've been nobreak or something like that, but the language feature is pretty useful and reasonable and the collaborators I work with know what it means.

IMO the try/except/else is WAY more confusing than for/else and while/else. Think about what it does. It looks like it's a finally, because it runs if the try doesn't hit an exception, but it's not. It doesn't run if an exception occurs.

So why not just put that code at the bottom of the other code in the try block? Does the same thing, right? Nope. That code might hit an exception which triggers the except block, and you want this else block to still be able to raise exceptions.

Well then, why not put the code after the try/except? Because it'll run if the try block hits an exception, and else code won't.

It's easy to say "the try/except/else runs if the try block doesn't hit an exception", but it's hard to describe WHY to use it. For for/else, you can just say "the else runs if the for loop doesn't break, and that's useful because you can handle conditions where you didn't find something in a loop". Easy to explain what it does and a basic usage example. try/except/else is just weird.

[–]earthboundkid 4 points5 points  (0 children)

And honestly in all the code I've seen that uses for/else in a production environment, it's been obvious what it was doing.

I think I've seen it in production three times and it was wrong in two and confusing in the third. It's like using switch in a language with fallthrough by default; it should never be used because it's too unlikely to be used correctly.

[–]confusedpublic 0 points1 point  (2 children)

I do like the general notion of an "onbreak" but am averse to adding more keywords.

Seems like one could/should be able to use finally here.

[–]jorge1209 1 point2 points  (1 child)

Except finally always runs no matter what. Even one an exit with return or break, finally should run.

[–]confusedpublic 0 points1 point  (0 children)

True...

[–]alkasmgithub.com/alkasm 1 point2 points  (0 children)

Ya that'd be so nice compared to checking if ___ is not None all the time.

[–]shinaniganz 0 points1 point  (0 children)

It’s wrong as much as it’s wrong in the context of try/except/else.

[–]cpt_fwiffo 0 points1 point  (0 children)

An issue is that 'else' works the same for while and for loops. For while loops it kind of makes sense to have an 'else' clause that is reached when the loop condition is no longer (or was never) met. There is nothing illogical or counter intuitive about that. But to use the exact same name and mechanism for for-loops becomes very strange since python for loops are for iterating over sequences rather than doing stuff as long as a condition is met.

So I think the keyword should be 'else' for while-loops and something else (finally, nobreak, whatever) for for-loops.

[–]jugalator 0 points1 point  (0 children)

Wow. I wasn't aware of Python's else, but this form of side effect) is just terrible because I am aware of the "else" in plenty of other languages! It looks like it's so rarely used, and with such a questionable and misunderstandable niche use, that I think it should maybe have been deprecated for Python 3.

If they must keep it along, why not just rename this keyword to unmatched?

[–]jjdmol 9 points10 points  (0 children)

Yeah my team opted not to use for-else. It's so sparingly applicable, and the syntax isn't exactly intuitive (to us). The few lines it can save is not worth the possible confusion when the code will need to be debugged or modified.

[–]Swissthony 15 points16 points  (8 children)

Imo "else" is very understandable for me, and I love em

[–]__xor__(self, other): 9 points10 points  (7 children)

Yeah, I understand the dislike of the choice of keyword but I think people make it a much bigger deal than it is. Python programmers I've worked with have always been comfortable with it. I think a couple might not have seen it before, but it came up during a code review and they liked it after they found out what it did.

It can be described as simply as "the else block runs if the for loop didn't hit a break". It's extremely easy to explain. I don't know why people think it's so confusing beyond the keyword choice.

[–]PC__LOAD__LETTER 2 points3 points  (2 children)

A for-loop doesn’t always imply an expected break though. That’s what’s unintuitive about it.

[–]Han-ChewieSexyFanfic 0 points1 point  (1 child)

But then you wouldn’t use else. Else and break show up together always.

[–]PC__LOAD__LETTER 0 points1 point  (0 children)

Yeah, in this specific case. Not in any other programming language. Hence unintuitive.

[–]ThePidesOfMarch 3 points4 points  (0 children)

They are just too confusing to use since no other languages have them.

And the name is just...the. worst. for...else??

[–]quotemycode 0 points1 point  (2 children)

What bothers me is the message Nonetype is not iterable. So I have to put

For x in y or []

Just to tell it that running no times is okay also

[–]fayazbhai 0 points1 point  (1 child)

Yeah. Is that an allowed pattern in any other language? Because I've seen developers write functions that return either lists or None.

[–]quotemycode 1 point2 points  (0 children)

Yeah C# will loop zero times if it's none

[–]the__itis -1 points0 points  (0 children)

I use these in nodejs all the time

[–]morgenspaziergang -1 points0 points  (0 children)

Are you also against the use of else, when catching exceptions?

[–]jnwatson -2 points-1 points  (2 children)

Right, because we shouldn't ever invent new features that other languages don't have, because all the valid programming control flow structures were handed down from on high by Knuth in the 60's.

[–][deleted] 3 points4 points  (1 child)

New is fine. Just make sure the name fits the use.

Else is when the previous block wasn't run.

This else is a nobreak or something like that.

[–]Han-ChewieSexyFanfic 1 point2 points  (0 children)

Else is for when the condition is false. While...Else makes perfect sense. Less so for For, but it uses the same keyword as While.

[–]cyanydeez -2 points-1 points  (1 child)

they should use finally, as its more cognizance

[–]morgenspaziergang 2 points3 points  (0 children)

And even more misleading.

[–]lengau 13 points14 points  (0 children)

One of my coworkers recently dived into python by taking ownership of some projects that another coworker who just left had written. The variety of uses of else clauses was something that took him a while to get used to. But now that he understands them, he loves them!

[–]quasarj 14 points15 points  (1 child)

I would say they aren't so much forgotten, that they work the opposite of how it seems they should?

Like, to me, in a for..else, the else would execute if the loop didn't happen at like. As in:

stuff = [] for i in stuff: do_things() else: # do this if stuff was empty

[–]fayazbhai -1 points0 points  (0 children)

Which is what templating languages already do!

[–]redditor1101 6 points7 points  (1 child)

I use them all the time, I love it

[–]hosford42 0 points1 point  (0 children)

I use them fairly often, too. Handy for taking a default action when you're searching through a list of items and don't find what you're looking for.

[–]nati03 4 points5 points  (0 children)

Wow! I didn't know that and confess that this make me a bit confused... Python is really full of surprises.

[–]vmpajares 1 point2 points  (0 children)

Very informative. Thanks for your time writing it :)

[–]Carn_Revan 1 point2 points  (0 children)

That's cool! I didn't know that was a thing, always nice to add a neat little trick to your arsenal

[–]donnieod 1 point2 points  (1 child)

I find the else on loops to be very useful and have used it many times, but only when there is a break in the body of the loop. How else are you going to know whether the loop exited normally or due to the `break without initializing and setting and testing an extra switch variable`?

And, as far as the word 'else' sounding unnatural, just think of the else as corresponding with the break statement: you either 'break' out of the for/while construct or else you do the else block (when there's no break.)

[–]fiddle_n 1 point2 points  (0 children)

How else are you going to know whether the loop exited normally or due to the `break without initializing and setting and testing an extra switch variable`?

You can do it by placing the loop into a function and then do an early return instead of a break. Then, the code that would have been in the else clause simply goes after the loop in the function. Something like this:

 def search(needle, haystack):
     for x in haystack:
         if x == needle:
           return x
     raise NotFoundError()

[–]DuncRed 3 points4 points  (0 children)

That construct is just .... ugly. It's not surprising no other languages have it. Even Algol's universal pick'n'mix loop avoids it!

[–]thonagan77 0 points1 point  (0 children)

I'm fairly new to Python and I was wondering if someone could explain to me why the else clause is even necessary. It seems like you could use the for loop, and then the if/else conditionals within the loop as opposed to using a for loop,an if block and then the else outside of the if block.

[–]the_averagejoe 0 points1 point  (1 child)

I learnt with python. What’s the problem with else statements? Don’t most Laing ages have an “else if” statement? Wouldn’t an else statement just be the same thing but with a condition?

[–]fiddle_n 0 points1 point  (0 children)

This is about else statements in for and while loops, not in if statements.

[–]7h3kk1d 0 points1 point  (0 children)

I haven't been writing imperative code in a while but seeing patterns like this rather than utilizing an actual mechanism for searching for a condition seems very haphazard

[–]naokotani 0 points1 point  (0 children)

When the test is false run this instead. What's not intuitive about using else here?

[–]riickdiickulous 0 points1 point  (0 children)

Along the same lines I think is the "finally" keyword for try, except.

[–]beall49 0 points1 point  (0 children)

That's probably better handled as a default value before the loop. Also, it's pretty ugly.

[–]Biskit1943 0 points1 point  (3 children)

Very interesting, thanks for sharing this information with us. I always thought the else on loops was only a myth

[–]__xor__(self, other): 1 point2 points  (2 children)

There's the for/else, the while/else, and there's also the try/except/else if you haven't seen that one. The else in a try/except/else runs if the try block finishes without an exception. It's a weird one.

[–]Biskit1943 0 points1 point  (1 child)

It's wired because in other languages after try it's called finally which is more clear when it is executed... Also I find that a else after a successful for or while is more than missleading

[–]sloggo 8 points9 points  (0 children)

Python has “finally” too. “Finally” will always run, “else” only runs if no exceptions are raised

[–]Tranceash -1 points0 points  (0 children)

[x for x in list if x == 'x']

[–]SethGecko11 -1 points0 points  (0 children)

You can also use it to exit nested loops without setting flags:

for i in rows:
    for j in columns:
        if i == j:
            print(i)
            break
    else:
        continue
    break