for server in servers:
if server.check_availability():
primary_server = server
break
else:
primary_server = backup_server
deploy_application(primary_server)
As it is shorter to do this:
primary_server = backup_server
for server in servers:
if server.check_availability():
primary_server = server
break
deploy_application(primary_server)
for server in servers:
if server.check_availability():
primary_server = server
break
else:
logger.warning("Cannot find a valid server") # <---
primary_server = backup_server
deploy_application(primary_server)
If you replace the assignment in the else clause with something side-effectful it does make sense. But even then it harms the readability of the code to such a ridiculous extent. I've never not regretted adding an else clause to a for loop. Any cognitive benefits from Python's approach to for loops closely mirroring natural language just go out the window.
If the team is familiar with Functional Programming concepts, I'd actually suggest
available_servers = (server for server in servers if server.check_availability())
primary_server = next(available_servers, backup_server)
deploy_application(primary_server)
which IMHO better conveys the intent to the reader (i.e. focus on "what to do" over "how to do it")
I would say that the main benefit of for/else is readability and ease of writing since nothing needs to be front-loaded. (The downside is an arguably redundant addition to the basic syntax, of course.)
This kind of search can be done a variety of different ways, and is worth abstracting, e.g.:
def first(candidates, predicate, default):
try:
return next(c for c in candidates if predicate(c))
except StopIteration:
return default
deploy_application(first(servers, Server.check_availability, backup_server))
Indeed. I was thinking of ways to generalize for the case where a default isn't desired, then decided against introducing that complexity in the example, then forgot that I could re-simplify further.
`more_itertools` is definitely on my shortlist of things I wish were in the Python standard library. (The list of things I'd like to see cut out, is probably considerably longer. But that's mainly on aesthetic principles; it's probably not creating a significant maintenance burden.)
And yet somehow I keep forgetting about it in the exact moments when it would probably make my life easier.
The shorter version that saves the else: line assigns primary_server twice: it is OK for an assignment but a potential problem if doing something less trivial, and very ugly style in any case.
I do like the else clause with for loops. However most people are not familiar with it, and also `else:` as a keyword is confusing. I always remember it as `no break:`.
That's still confusing though. The problem here is that `else` is semantically attached to `break`, but syntactically attached to the body of the loop. The latter makes it look like it executes if the loop body didn't, if you interpret it in the most straightforward way.
IMO a better design would be to have a block that always executes at the end of the loop - there's even a reasonable keyword for it, `finally` - but gets a boolean flag indicating whether there was a break or not:
for server in servers:
if server.check_availability():
primary_server = server
break
finally did_break:
if not did_break:
primary_server = backup_server
Or better yet, make `break` take an optional argument (which defaults to `True` if unspecified), and that's what you get in `finally`. So this could be written:
for server in servers:
if server.check_availability():
break server
finally server:
primary_server = server if server is not None else backup_server