I’m new to this community, but regarding this discussion I wonder how you all think about this edge case of raise in finally:
def cleanup():
# oops, this is buggy
raise Exception("cleanup failed")
def feature():
try:
name = input("name?")
print("hi", name)
finally:
cleanup()
def main():
try:
feature()
except Exception as e:
print("Feature failed:", e)
print("Continuing")
If the user provides a name, the code logs the cleanup error and continues.
If input() raises something like EOFError, the same thing happens.
But if the user hits Ctrl + C, the KeyboardInterrupt is replaced by the cleanup error, and main() swallows it. The program keeps running, which is very surprising if you only look at feature() or main() in isolation.
Yes, letting finally raise unchecked is an anti-pattern. But it happens, and in practice the masking isn’t always obvious: as @tim.one noted in #57, both exceptions are shown via chaining. The real footgun is when an outer exception handler swallows the second exception, unintentionally discarding the original BaseException (like KeyboardInterrupt).
FWIW, we are currently having a very similar language discussion in the Squeak/Smalltalk community. Smalltalk supports non-local returns (similar to Kotlin), allowing us to do things like:
[^1] ensure: [^2]
which is the direct equivalent to Python’s:
try:
return 1
finally:
return 2
However, in Squeak, suppressing an exception uses the same non-local return mechanism as above:
[[^1] ensure: [self error]]
on: Error do: [:ex | ex return].
^3
corresponding to Python’s:
try:
try:
return 1
finally:
raise Exception()
except Exception:
pass
return 3
Because of that, a runtime check forbidding non-local returns would automatically forbid suppressing exceptions in ensure/finally, too (in the Smalltalk architecture a runtime check during unwinding makes more sense than a static compile time check). We’re debating whether that’s desirable. I’m sharing this perspective here because if Squeak’s and Python’s conceptual unwinding models are similar enough to each other, I hope it might be relevant for consideration of PEP 765 as well.
I’m curious about your thoughts on that. Is raising from finally just as evil as returning from finally in your opinion? Is this something you would cover by PEP 765 (or any good linter) as well if it was detectable at compile time?