Benefits of using for else syntax

Consider the following code:

found = False
for username in userList:
    if username == userInput:
        found = True
        checkPassword(username)
        break
if not found:
    doSomething()

I have just learnt about the for...else... syntax, what are the benefits of using for...else... syntax? (efficiency? optimization? additional features? readability?)

The code using for...else... syntax:

for username in userList:
    if username == userInput:
        checkPassword(username)
        break
else:
    doSomething()

I am struggling whether I should change all my related code.

edit: add a break when user found

The problem with the for else is that many people cannot remember what it does.

I for one do not use it because I always have to check the docs to remind myself what it does.

I still use the first form with the found variable becuase its easy to understand.

And I’ve been using python for decades…

2 Likes

Short answer: No, don’t change all your code just because you have a new style available :slight_smile: Notably, your two examples are NOT identical, so you would need to be sure that you are okay with switching to break semantics. Here’s something that IS basically equivalent:

found = False
for username in userList:
    if username == userInput:
        found = True
        checkPassword(username)
        break
if not found:
    doSomething()

With this version, it’s much clearer; as long as you aren’t using found further down, it’s safe to remove the variable and replace if not found: with else:, as the semantics are the same. But this is exactly why it’s not worth making wholesale code changes just for the sake of style.

1 Like

Thanks for your reply! :heart:
I agree that the else keyword is somehow confusing.

I am wondering if there is any (huge?) performance improvement that would convince me to change my existed codebase.

Performance isn’t really the point here. You may find that it’s a little faster (less messing around with setting and getting variables), but that’s almost never the clinching argument. Focus on expressiveness and how well either form reads. This is a case where smart people can and will disagree on what is best, and even a single person may well disagree with themselves over time (I’ve certainly changed my coding style significantly, multiple times, over my career).

2 Likes

Thanks for your reply! :heart:

You are correct, I forgot to add a break when user found in my first example. I will edit the post later.
(edit: I was promised that every element in userList is unique, so I believe that it is not a big deal in my first example?)

I should not change my codebase then.

In terms of readability, I am actually struggling thinking which one is better. :thinking:

Yeah, it’s not particularly significant in this example; I just mentioned it as a reason to not go through all of your code to change it, since you have to be VERY careful of edge cases.

1 Like

I like and use while-else and for-else more than some other people. Like if-else, the else part of while-else executes when the if/while condition is false, and see for loops as a specialized while loop, with the loop condition being that next(iterable) returns an item. But I would not suggest changing existing code when not otherwise refactoring.

2 Likes

When I would have code where this would be suitable, generally I leverage any/all if I’m not interested in the match, next with a generator if I am.

try:
    user = next(u for u in users if u.name == name)
except StopIteration:
    doSomething()
else:
    checkPassword(u)
user_exists = any(u.name == name for u in users)
3 Likes

If you already can’t decide, after being used to one way and only just having discovered the other… I guess you’ll soon prefer the new way if you get more used to it. Maybe think “nobreak” (source) everytime you type that “else”.

1 Like

You are correct, I forgot to add a break when user found. I will
edit the post later.

I should not change my codebase then.

Aye.

In terms of readability, I am actually struggling thinking which one is
better. :thinking:

To a degree, readability has to do with the both the overtness of the
test (eg the found=True approach) and the familiarity with the idiom
(as mentioned, plenty of us have to look up the correct meaning of
for-else, myself included). But personally I’m taking that to mean
I’ve not internalised it.

One nice thing about the for-else is that if you see it, you pretty
much know that the loop goes “break on finding the target situation”
(versus plenty of loops which always run through all iterations etc).

Also, it makes the “which if the situation isn’t found” mode much more
overt.

So personally I’m trying to use it more, if only to learn it. In fact, I
used it just last night:

 file = book.encryptedfiles[filename]
 for userkey in lib.userkeys:
   with Pfx("userkey %s", userkey):
     try:
       plain_contents = file.decrypt(userkey, contents)
     except ValueError as e:
       warning("%s", e)
       continue
     try:
       file.check(plain_contents)
     except (IndexError, ValueError) as e:
       # Parse failures mean the key is probably wrong.
       ##warning("file.check fails: %s", e)
       continue
     break
 else:
   raise ValueError(
       f'could not decrypt using any keys from lib.userkeys:{lib.userkeys!r}'
   )

where it’s now clear that if we can’t decrypt contents then we’ll bail
out with an exception.

Cheers,
Cameron Simpson cs@cskk.id.au

2 Likes

I used to avoid for-else because

  • code in long lived projects is read more than it is written
  • not everyone remembers the syntax easily

However, I have recently come to think that it is the clearest way – to read! – a check for an empty iterator.

Others have given this example above, but in the abstract they are variants on

for item in my_items():
    foo(item)
else:
    bar()

EDIT:
Strike the above. I got my examples mixed up. This post is highly inaccurate. Correct information follows in thread.

This does NOT check for an empty iterator.

>>> for item in []:
...     print(item)
... else:
...     print("else")
... 
else

It checks whether you broke out of the loop. Nothing more and nothing less.

You could use it to check for an empty iterator if you only need one element out of it, though:

for item in my_items():
    foo(item)
    break
else:
    bar() # NOW this happens only if no items
1 Like

:man_facepalming:
That was embarrassing. Thank you for calling me out on this.

It’s not empty-iterator checking that I was thinking of, and I was lazy and didn’t double-check what my go-to example in my homedir contains, which is…

for item in collection:
    if condition(item):
        break
else:
    print('condition was never met')
1 Like

Ah yes, THAT is a perfect example: a search, where the “if… break” effectively matches the “else”.

2 Likes

And that is the confusion that makes me not use for else.