Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

TFA's use-case for for/else does not convince me:

    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)


What about this?

    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)


Yes, that might be a better example.


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))


Instead of catching the `StopIteration` exception, you can simply provide a default case to `next` :

    next((c for c in candidates if predicate(c)), default)


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.


Great, first() comes part of more_itertools !


`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:`.


I think of it as "otherwise:"


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




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: