# PEP 765: Disallow return/break/continue that exit a finally block

**URL:** <https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348>\
**Category:** PEPs\
**Created:** [November 16, 2024, 10:28am UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348 "2024-11-16T10:28:23Z")\
**Posts on this page:** 1\
**Showing post:** 177

<div class="post-metadata">

**Author:** ![LinqLover](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/linqlover/32/31362_2.png) [@LinqLover](https://discuss.python.org/u/LinqLover)\
**Post date:** [October 1, 2025, 4:20pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/177 "2025-10-01T16:20:29Z")

</div>

I’m new to this community, but regarding this discussion I wonder how you all think about this edge case of `raise` in `finally`:

```python
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](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/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.](https://lists.squeakfoundation.org/archives/list/squeak-dev@lists.squeakfoundation.org/thread/67TWJ3ESPAO6UPAS4CVX4SHDVHXURGH2/#SDWIONZDTW5E5F35IC4DPPDN64VCQO2A) Smalltalk supports non-local returns (similar to Kotlin), allowing us to do things like:

```smalltalk
[^1] ensure: [^2]

```

which is the direct equivalent to Python’s:

```python
try:
    return 1
finally:
    return 2

```

However, in Squeak, suppressing an exception uses the same non-local return mechanism as above:

```smalltalk
[[^1] ensure: [self error]]
	on: Error do: [:ex | ex return].
^3

```

corresponding to Python’s:

```python
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?

---

_[View the full topic](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348)._
