One thing that could be done is to trigger a compile-time error if the loop body doesn’t contain a break at all (then the else: clause is unreachable). I’ve seen newbies trip over that more than once on Stackoverflow.
Because finally is the only kind of suite that can be entered when an exception has already been raised. Indeed, it’s the purpose of finally to run code “no matter what, exception or not”.
The mental model then is that the exception (if any) is temporarily put on hold, until the finally suite terminates. At which point the exception is (re)raised.
So for “orthogonality”, preserving that sane mental model, continue, break, and return “should be” allowed: but ignored if (and only if) an exception is pending! They terminate the finally suite, and so the exception should be (re)raised.
Which is an incoherent mental mess. Despite all the decades I’ve been using Python, I was astonished to see this:
I never suppressed the ZeroDivisionError, so it’s jarring that it didn’t propagate. It was, I’m sure, never intended that finally suppress exceptions. That’s a job for except.
Yes I agree with the re-raise analysis, but see no real difference in break/continue/return inside an except block or a finally I really would prefer the same behaviour. I can always use sys.exc_info to discover information about unhandled exceptions although that is a bit clunky.
I don’t find anything special about the exception suppression; after all except blocks can choose to suppress an exception by just not re-raising.
import sys
for i in range(3):
try:
raise (ValueError if i==2 else RuntimeError)(i)
except ValueError:
print('raised')
continue
finally:
print(f'finalised {sys.exc_info()==(None,None,None)}')
continue
else:
print('else')
I’m sure there are many other gotchas in python which are not regarded as syntax errors mutable default arguments are often used wrongly (by me).
I do not think that it should go so far. Such code can occur temporary in process of developing, after commenting out some lines. It is like making redundant else an error if the if block ends with return.
But SyntaxWarning would be good here. else after loop without break is not just a matter of style. It is alway a result of misunderstanding this syntax. It works, but not as was intended. It is similar to assert with parentheses.
On the contrary, I had to re-read your post multiple times to find what would be wrong with your example. I associate try: with exception handling more than except, as in “something will happen when an exception is raised”, and the fact that this “something” happens in finally seems perfectly logical to my. Maybe I haven’t used python for enough decades
Now I understand the possible problem. In this case finally functions as a bare except:…
This is a very good illustration of the problem with the else keyword. The feature is useful, but the spelling is very confusing! I liked the nobreak: proposal, it would be the obvious way to do it – aside backward compatibility problems and having two keywords for the same construct.
I support disallowing such code. While I can make legitimate examples that use it, it is more convenient and error-proof to use separate except and else clauses in such special cases.
I only wonder, was it necessary to create a new PEP rather then reopening the old PEP?
The old one was rejected 5 years ago and I think a fresh PEP is fine. It makes it clearer to see what has changed since then, and we can leave the old one as a historical document with its historical rejection notice. Also, each PEP has a different pair of authors.
We can however add “Replaces: 601” to the new PEP and “Superseded-By: 765” in the new one to show their relation.
I may have missed it in the earlier discussion (but hopefully I didn’t miss it in the PEP text) - why are we forbidding the construct rather than just making exceptions re-raise on exit from finally, regardless of how we leave the block?
Choosing to disallow it entirely in except* doesn’t seem like enough precedent to remove it elsewhere. We disallowed backslashes in f-strings for a while because we didn’t have a consistent model, but the intent was always to allow them when we could explain it properly. “Finally blocks executed due to an exception will always re-raise the exception after completion, even if exited early due to control flow (other than a new exception)” seems an easy enough proposal to explain.
Well, this one is going to face that same risk The difference would be that people who have used it correctly get to keep using it, whereas this proposal even breaks them.
As Guido mentioned earlier, it’s more about composability than usefulness. There’s always another way to do it, and the recent trend of languages is to be more controlling of the developer, so I’d totally not expect any new language to do it.
Any block of code can be exited early by control flow. This rule applies everywhere, for better or worse, and consistently. The only case being addressed here is a conflict between two early exits, and it seems we chose the wrong priority at some point. That’s a solid argument for fixing the prioritisation (that is, a raised exception should win over break/continue/return), but IMHO a weak argument for making a finally block into a special kind of code block that doesn’t support basic control flow.
(I do recognise there are limits to this argument, but it ultimately comes down to how easy it is to explain and understand what’s happening. I’m totally okay with restricting yield and await in certain places, since those are hard enough to explain already. I’m not convinced that break/continue/return are so hard.)
I don’t think so - a SyntaxWarning doesn’t stop the code working, and it’s clearer than your code suddenly raising some exception that was previously swallowed.
If you wanted to swallow the exception then it can be correct to return from a finally block. Your suggestion to reraise the error would silently break code that uses the existing return from finally feature intentionally.
Except the only thing to suggest that this is intentional is that it is how it’s implemented. The design and use of code blocks elsewhere wouldn’t lead to this being assumed, and multiple people over the various issues have assumed that the exception would still be raised even if the finally block exits early.
There’s a very obvious way to handle an exception when writing a try/except block. If someone has deliberately chosen to handle exceptions in that very construct but without using that construct as intended, they ought to be less surprised when they find out that they should’ve just used it as designed.
No, it would noisily break the code Exceptions that were being swallowed would no longer pass silently. Code that intentionally uses return from finally to change the return value (weird, but okay) or to stop executing code in the finally block (when not raising) would continue to work exactly as they do today.
The alternative is to noisily warn about the code in the importer, making it difficult for libraries to suppress when they know that it’s okay.
But I’m not trying to argue the alternative, or else I’d just write a counter-PEP. I’m trying to establish why it isn’t even mentioned in the PEP as a potential alternative, given that it results in less dangerous behaviour and fewer compatibility issues.
Specified, yes, but you don’t get to the specification without going past the whole bit about “use except to handle exceptions”.
Also, it’s only there because people noticed it was missing and the behaviour wasn’t documented, so it got documented. I’d take Guido’s statement of intent (and Tim’s memory) over what’s in the documentation today - plenty of stuff gets specified after it’s implemented.
Yes, it’s a bit surprising, but it does follow logically. After the first block finishes, the finally block always runs, and return None exits the current function with None as the result. You probably shouldn’t write code that way, but it’s not fundamentally wrong or dangerous, just unexpected.
Now, in the cases where fetch raised an error, we have a problem. You code silently suppresses an error, and the lack of except: block suggests that wasn’t your intent. So is it better if the error is still raised? Or better if everyone who uses your module gets a warning (that breaks their own tests/users) whether that error ever occurs or not?