News for hackers. Stuff that matters.
Re:A Python Guide for the Ages by framecowbird on Saturday December 25, 2021 @12:52AM (#29680831)
There are a couple of language features in Python that I thought were cool when I discovered them, but actually have never ever find a need for them in my code. The first is using an "else" clause in a "for" loop, and the second is returning a value from a generator. Curious whether anybody actually uses either of those.
Re:A Python Guide for the Ages by anamax on Saturday December 25, 2021 @02:04AM (#29681145)
I've used both, for with else more often. It avoided some booleans and if's that would have been much less clear/easy to get wrong.

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.

Re:A Python Guide for the Ages by greggyb on Tuesday December 28, 2021 @05:31PM (#29719098)
Raymond Hettinger has a good section on this, tracing it back Knuth's discourse on structured programming in the face of 'goto'. He suggests calling the keyword 'nobreak', rather than 'else'.

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

Re:A Python Guide for the Ages by asicsp on Saturday December 25, 2021 @03:36AM (#29681520)
From Python docs (https://docs.python.org/3/tutorial/controlflow.html#break-an...):

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

Re:A Python Guide for the Ages by AlphaSite on Saturday December 25, 2021 @02:44AM (#29681310)
It’s consistent with try…else and if…else so I actually think it’s ok.
Re:A Python Guide for the Ages by 63 on Saturday December 25, 2021 @02:49AM (#29681329)
"else" usually means "instead of" or "otherwise" in those patterns. If the original case doesn't run, then the else case runs instead.

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.

Re:A Python Guide for the Ages by housecarpenter on Sunday December 26, 2021 @11:38PM (#29699302)
It depends on whether you think of the break as success or failure. When I write a loop with a break it's usually some kind of search where the break happens once you've found something, and in that case the break is more of a success.
Re:A Python Guide for the Ages by jermy on Saturday December 25, 2021 @06:06AM (#29682246)
The way you describe is how it worked in zope templates (allowing a 'no items here' text if there was nothing to iterate over in a list) - it was frustrating that the later python implementation was different.
Re:A Python Guide for the Ages by ptx on Saturday December 25, 2021 @09:34AM (#29683619)
You could read it as "either process all of these elements, or otherwise do this other thing". If we break, we're not processing all of the elements (the normal case), so we do the "else" part instead.

Edit: This is wrong. See replies.

Re:A Python Guide for the Ages by 63 on Saturday December 25, 2021 @10:01AM (#29683815)
That would make total sense if it were correct. That's exactly how I think most people would intuit it

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.

Re:A Python Guide for the Ages by ptx on Saturday December 25, 2021 @10:34AM (#29684085)
Hmm! Yes, you're right. Even though I knew exactly how it works and I sometimes use it, I still got confused, so maybe it is a confusing feature after all. :)
Re:A Python Guide for the Ages by 63 on Saturday December 25, 2021 @12:59AM (#29680862)
I actually saw someone use that for-else pattern in advent of code earlier this month. I believe the consensus was that it was definitely the perfect choice for the given use case, but neither he nor I would ever dare use it in production because nobody knows about it and it's super unintuitive.

I think the idea is cool (albeit rarely useful), but a different word than "else" for the same functionality would go a long way.

Re:A Python Guide for the Ages by globular-toast on Saturday December 25, 2021 @06:14AM (#29682290)
Instead of avoiding it in production code, it's better to understand that it's the perfect place for a comment. For example:

   for ...:
      ...
   else:
      # loop ended, no positions found
      return ...
Re:A Python Guide for the Ages by nneonneo on Saturday December 25, 2021 @03:12AM (#29681416)
I used it a bunch in Advent of Code as well. It’s very useful for “search” loops, which should break on finding the target, or do something else if it isn’t found.

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!

Re:A Python Guide for the Ages by mixmastamyk on Saturday December 25, 2021 @08:11PM (#29688720)
Use with a short comment:

    for … :
        …
    else:  # break didn’t occur
        …
I typically make it even shorter, but this is probably the most readable.
Re:A Python Guide for the Ages by dr_kiszonka on Saturday December 25, 2021 @03:07AM (#29681400)
I find generators useful when working with very large data structures because generators can be pretty efficient. You can also use them to write little helper functions. Have a look at this package and its source [0]. Oh, and don't feel bad if you never have a need for generators! For the longest time I felt like a steal for rarely using classes but I am over it now :)

0. https://more-itertools.readthedocs.io/en/stable/

Re:A Python Guide for the Ages by quietbritishjim on Saturday December 25, 2021 @06:02AM (#29682238)
They didn't say they hadn't used generators at all. They said they hadn't made use of "returning a value from a generator" which is different from the usual method of yielding from them.

    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.

Re:A Python Guide for the Ages by housecarpenter on Monday December 27, 2021 @12:15AM (#29699521)
I used generators with return values once when writing a lexer. I had generators for each of a bunch of different states of the lexer (e.g. skipping over whitespace, reading a numeric literal, reading a string literal, etc.). Each generator would yielded the tokens that it could and then either return one of the other generators (if a state transition was required) or None (if it reached the end of the input and the state was one where this wasn't an error condition). The whole thing was tied together with this function:

  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.
Re:A Python Guide for the Ages by tjpnz on Saturday December 25, 2021 @02:57AM (#29681370)
I prefer using the else clause to maintaining an "is_found" flag. Although this is lesser known syntax so I probably wouldn't use it when multiple developers are involved.
Re:A Python Guide for the Ages by nneonneo on Saturday December 25, 2021 @03:14AM (#29681433)
Yeah, the biggest risk with for-else is that some inexperienced dev comes along and misreads the else as belonging to an if statement inside the for (or, worse, attempting to “fix” it). I’ve seen this happen more than once…
Re:A Python Guide for the Ages by xmcqdpt2 on Saturday December 25, 2021 @07:44AM (#29682824)
Yes I always comment on it for this reason, sometimes (if I know the code will be maintained by someone with less python experience) with a link to the standard doc.

It's actually a super useful pattern for any kind of search iteration. I've used it many times.

Re:A Python Guide for the Ages by xmcqdpt2 on Saturday December 25, 2021 @07:55AM (#29682905)
for/else is actually super useful for searching through a sequence or for iterating with a possible failure. Something like,

  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")
Re:A Python Guide for the Ages by montebicyclelo on Saturday December 25, 2021 @09:35AM (#29683633)
What's the advantage over the following?:

  for i in range(n):
      if search(i) == val:
          break
      raise KeyError("not found")
  found_index = i
Re:A Python Guide for the Ages by xmcqdpt2 on Saturday December 25, 2021 @10:36AM (#29684112)
That version doesn't work. It raises KeyError on the first iteration if the if statement is false.

The 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.

Re:A Python Guide for the Ages by montebicyclelo on Sunday December 26, 2021 @02:30AM (#29690467)
Aha, thank you. Yes, it's plausible that my brain did pair the `else` with the `if`, even though it knew it was supposed to be paired with the `for`.

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")
Re:A Python Guide for the Ages by Arrrlex on Saturday December 25, 2021 @09:59AM (#29683803)
I think this snippet would raise a KeyError whenever the index you're searching for is greater than 0.
Re:A Python Guide for the Ages by globular-toast on Saturday December 25, 2021 @06:11AM (#29682272)
for/else is unfortunately named (although makes sense if you think about the hidden if/else in a loop). It is essentially use to distinguish between "this loop ended naturally" (the else condition) or "this loop was broken".

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.

Re:A Python Guide for the Ages by CodeGlitch on Saturday December 25, 2021 @03:36PM (#29686738)
Walrus operator.

I don't think I've seen it in the wild either. Perhaps too new?

Re:A Python Guide for the Ages by mixmastamyk on Saturday December 25, 2021 @08:22PM (#29688777)
It’s used a lot in simple situations. However never seen it used for multiple values, which was the reason given for using := instead of the “as” keyword used elsewhere. So I cringe every time I use it.