Jump statement in try except finally block

Hi all,
can someone say why this is stuck in a infinite loop?

Code

while True:
    print("why is this happening")
    try:
        break
    finally:
        continue

Output

why is this happening
why is this happening
why is this happening
...
2 Likes

Because control flow statements (break, continue, return, raise) in finally suites overwrite whatever else would have happened if executed. This is almost always a bad idea and will lead to confusing code.

Is that documented somewhere?

Yes: 8. Compound statements — Python 3.12.4 documentation

That’s of course where I had immediately looked. Where there exactly is this behavior documented? I don’t see it.

This exact edge case is not directly listed. IMO it follows well enough from

If the finally clause executes a return, break or continue statement, the saved exception is discarded:

And

When a return, break or continue statement is executed in the try suite of a try…finally statement, the finally clause is also executed ‘on the way out.’
The return value of a function is determined by the last return statement executed.

But I don’t think anyone would mind if you suggest an improvement of the docs there.

5 Likes

I had seen all that and I disagree that any of that documents this case. The ‘on the way out’ even rather sounds like the opposite to me.

Good, then we disagree. Go propose a change if you care.

1 Like

Maybe combining with this sentence in the documentation of break

When break passes control out of a try statement with a finally clause, that finally clause is executed before really leaving the loop.

I guess one can think, or not, that the breaks get queued for execution after the finally is done. The effect is the same, the break would only happen after the loop completes, which in this case is never.

FWIW, it works the same way in other languages that have the same suite of try, finally etc. keywords.

Similar to the “on the way out”, to me that rather sounds like the “leaving the loop” is still happening. Otherwise it’d be “instead of really leaving the loop” in this case. I think it’s just not clear either way. Unlike the double return, which is explicitly and clearly covered:

The return value of a function is determined by the last return statement executed. Since the finally clause always executes, a return statement executed in the finally clause will always be the last one executed: [example]

‘on the way out’ of try, not the loop :rofl: it’s all there

1 Like

What’s the point of repeating that as if we hadn’t discussed it already?

sorry, I must have missed some of it, I was just curious about the implementation problem prior to 3.8 ? Is that outside this post :woozy_face:

You can probably check the changelog of 3.8 and from there search back through the PRs/issues.

check this documentation link

I would look at it this way: there is a virtual program counter that determines which statement is executed next. The value of this counter isn’t actually used until it is time to determine what to next execute, which doesn’t happen until the finally clause executes. Thus, in a sense, continue overwrites the value written by break before the value of the program counter is requested.

That is, the break statement says “Set the program counter to 7”, but before you actually go to line 7, you have to execute continue, which says “Set the program counter to line 2”. Now that the try statement is complete, you look at the program counter to see where to go next, and the program counter is set to 2.

2 Likes

That is consistent with the result of unwrapping the loop into (infinitely many prints, followed by infinitely many break)

print("why is this happening")
print("why is this happening")
print("why is this happening")
...
break
break
break
...

We get this (\omega\cdot 2)-list of instructions, since each finally does a continue “before leaving the loop”. So, all passes over the loop’s code get done before all the “leaving of the loop”.