The latter, because the exception will only be raised in some edge case in prod so the whole thing would slip through testing.
But surely the fix to avoid the warning is to make sure that the error is actually raised? Both of the āError Casesā shown in the research would be automatically fixed by reraising, and neither would get any worse in production. The reactions from most authors suggest that they hadnāt intended to suppress all exceptions, and so reraising would meet their expectations.
Iām missing some crucial piece here that I assume you found during the research. Can you point me to it? (Or better yet, call it out in the PEP in the new section where youāll reject automatic reraising )
The intention of my ādeletedā question is to demonstrate optional tasks - itās good if they succeeded but it does not harm if they didnāt. Just carry on anyway (hence catch all and return).
I agree that the demo code I wrote will be much clearer if āexcept :
ā is used instead of finally:
. (edit: and make the behavior correct)
folded
I vaguely recall there be a discussion on making bare except
equivalent to except Exception:
i.e. skipping other BaseException
subclasses. If this change takes into effect, then finally
will become the only truly catch-all clause unless user explicitly write except BaseException
.
Not just clearer, it would be correct But if the return
in the finally was conditional, then it may still have been correct. An unconditional return in a finally block is likely not ever going to be useful.
Only if you also want to catch success cases. Iām not sure where this argument is leading, but I think you may be confusing things (either yourself, or at least introducing confusion to the discussion). In no way should finally
be thought of as a ācatch-all clauseā - thatās not the purpose for it. It isnāt meant to ācatchā anything at all, itās meant to execute code regardless of whether the operation succeeded or failed.
Itās easier to respond to a SyntaxWarning
pointing to the place in the code where the issue is, than to some random exception that started showing up when you upgraded to 3.14, and you donāt know where itās coming from and why it didnāt happen in 3.13. How would you know that some finally
used to suppress it?
Also, there were a handful of non-error cases where they did intend to suppress the error, and they assumed that the semantics are as stated in the documentation.
Indeed, I will add a section to the PEP about this.
But that forces everyone to respond and churn, while a linter rule can also point straight to the line of code.
Iād prefer to not turn the compiler into a linter. Unless weāre strictly defining the code as illegal, itās legal. And Iām not convinced this use should be illegal.
Agreed. My apologies. Iāve folded that paragraph. (I am new to this kind of discussion and am still learning how to properly communicate my thoughts).
Glad to have you here participating
We could add a note to the exception to explain this. (exceptions are now re-raised on exit of the finally block)
But even with that, this would only be feasible if you get notified of the behaviour change during testing (not specifically whether or not the exception is supressed). So, we might want a syntax warning either way, for at least 1 Python version. And of course, code wouldnāt be compatible with both old and new Python versions.
To me it seems harder to explain than the current behavior, and no more useful.
The contract of the finally
block is that the code within it executes, even if an exception is raised. I donāt see how one could claim that the statement return None
has executed if the function does not then return the value None
.
These control flow statements would then become unique in a finally
block in that, when reading the code in the finally
block, one would have to keep in mind that these particular statements are really provisional; they may or may not actually have their stated effect, depending how the finally
block was entered.
IMO, āa statement that executes has the effect it says it doesā is a much stronger mental-model invariant than āonly except blocks can silence exceptions.ā (The latter is already falsified by context managers.)
So I think that if these statements are to be legal at all in a finally
block, the current semantics for them are preferable.
I do agree that this option should be discussed in the PEP as a rejected alternative.
Yup! Good catch.
Thatās a good framing, I canāt disagree. Though Iād still argue that making control flow illegal in a block thatās being used as a finally
block isnāt worth the inconsistency.
Agreed on the first part; the second part makes perfect sense in the context of the try/except
statement, but has no need to generalise further. The fact that itās an except
statement determines whether or not the exception will be automatically reraised independent of the content of the code block. Nothing in that disallows other syntax from also handling exceptions (itās the with
syntax that silences the exception; the context manager merely advises the syntax).
This is also an option that minimises breakage, so Iām happy with it as a result. Though it does just mean rejecting this PEP, so itās not as interesting to discuss
Iām still presuming that some reason was found why itās urgent to cause some breakage in order to begin to fix other breakage. Iād love for that reason to be clearly explained, especially since Iām going to have to explain it to my (corporate) users who are already very nervous about updating to 3.12.[1]
Unjustifiably nervous, Iāll add. But even if I tell them that theyāre still going to be ultra careful, and every random little break is going to come back at me with āwe rolled back a million-machine deployment because of this message, why is it there?ā (not a hypotheticalā¦) ā©ļø
I donāt know if there was a single precipitating reason, but the PEPās argument (with evidence) is that in practice this will not break ~any code, it will just alert people to existing bugs in their code, because ~nobody is currently using this feature correctly and intentionally.
Personally I tend in principle towards āallow compositions of syntax that have a reasonable interpretation, even if itās not obvious at firstā and āthe compiler is not a linterā ā but I do find it hard to argue with the research evidence in the PEP, which leaves me somewhere around +/- 0 on the PEP.
(Of course, a lot of existing code is not visible to that research; Iām sure that somewhere someone is using this correctly and will be inconvenienced by making it illegal.)
Youāll get there after another decade . āSomethingā is nuanced: it may be suppressing the exception entirely, or it may be executing some code regardless of whether an exception occurs. except
is the job of the former, and finally
the job of the latter.
The only coherent contract finally
can make is āthe suite will be executed no matter what, but if an exception was pending when the suite was entered, that exception will be propagated when the suite exitsā. That contract is currently violated if the finally
suite exits via break
, continue
, or return
, and thatās a design error.
In some cases it may not matter, but the research they put into writing this PEP showed that this (mis)feature does a wrong thing more often than not in real code.
Yes, but only because my example had a return
in the finally
suite. In the absence of break
/.continue
/return
, finally
acts in no way like any spelling of except
. As at the start, except
and finally
have very different purposes. āWell, sometimes finally
acts like a bare except
too, but sometimes not, depending on whether the finally
suite falls off the endā is a mental mess.
While Iām no longer Guidoās oracle, I can speak for what he would have thought 30 years ago: no frickinā way .
Following same logic, raise A()
within finally
is also suspect, isnāt it?
Oddly enough, that one does not suppress the original exception. Instead it adds a new one to the chain:
>>> z = 0
>>> def f():
... try:
... x = 1/z
... finally:
... raise NameError()
...
>>> f()
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
...
NameError
How about this then: within finally Iād raise a new exception and catch it and then fall through? With or without a helper function call?
Does what I expect: catches the new exception internally, and goes on to raise the original exception when the finally
suite falls off the end.
I donāt see why that would matter, so didnāt try it. Youāre free (and encouraged!) to run your own experiments, though.
Since we added exception chaining, at least (around 3.3 or 3.4, IIRC?). Clearly someone decided then that preserving the original exception mattered, despite the previous behaviour being to suppress it.
As an outsider, I find the idea that the removal of āforā¦elseā is even being considered at all to be shocking.
Python already has a reputation as having the worst long-term language-level stability and churn of any widely-used language. Backwards-incompatible changes should be extremely well-justified. In this case, āforā¦elseā is the Python feature I find myself looking for the most when Iām using other languages. Itās incredibly useful and widely used. Maybe a better syntax could be chosen and the current one could get a warning, but the idea of making that an error at any point should be beyond consideration.
For the actual subject of this PEP, Iām not thrilled but if thereās data showing itās genuinely rarely used, okay, thatās fair enough. But āforā¦elseā is an awesome construct that sets Python apart in the best way and itās widely used. Please donāt create more compatibility churn, and cause the community lose more unmaintained libraries yet again, without good reason.