# 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:** 20\
**Page:** 4

<div class="post-metadata">

**Author:** ![nedbat](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/nedbat/32/8744_2.png) [@nedbat](https://discuss.python.org/u/nedbat)\
**Post date:** [November 19, 2024, 4:10pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/62 "2024-11-19T16:10:46Z")

</div>

> [@breaker](#):
>
> As an outsider, I find the idea that the removal of “for…else” is even being considered at all to be shocking.

I don’t think anyone here is seriously suggesting removing for/else.

---

<div class="post-metadata">

**Author:** ![jamestwebber](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jamestwebber/32/12799_2.png) [@jamestwebber](https://discuss.python.org/u/jamestwebber)\
**Post date:** [November 19, 2024, 4:31pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/63 "2024-11-19T16:31:46Z")

</div>

It _was_ mentioned a few times, but it’s not part of this PEP so it doesn’t really need defending now.

---

<div class="post-metadata">

**Author:** ![tim.one](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tim.one/32/282_2.png) [@tim.one](https://discuss.python.org/u/tim.one)\
**Post date:** [November 19, 2024, 7:01pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/64 "2024-11-19T19:01:36Z")

</div>

> [@breaker](#):
>
> As an outsider, I find the idea that the removal of “for…else” is even being considered at all to be shocking.

As an “insider” 😉, let me clarify: it’s not being considered.

Guido designed Python. `for/else` was his idea to begin with. He’s sometimes joked that it’s the only wholly new idea he had.

Guido was impressed by that analysis in _this_ PEP showed not just that the construct in question is rarely used, but does a wrong (unintended by the programmer) thing in a large majority of the cases it _is_ used. It’s clearly a “bug magnet”, and to an extraordinary degree.

Guido did not go on to suggest getting rid of `for/else` too, but only that he’d like to see a similar kind of “real world” analysis done for that construct. I would too, for that matter, but already explained why I think it would be very much harder to do. So much harder that I doubt anyone will do it.

But, _if_ they did, _and_ discovered that over 70% of real-world uses were incorrect, ya, then the language has a real problem. _One_ possible way of addressing that would be to deprecate `for/else` too.

But that’s all several layers of highly unlikely (according to me) hypotheticals away from where we are here.

It’s natural for topics to branch off into vaguely related areas. But any more talk of `for/else` here would probably justify splitting those off into a different topic. It’s emphatically not _the_ issue in this topic, and has only weak purely hypothetical connections.

---

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [November 20, 2024, 12:39pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/65 "2024-11-20T12:39:39Z")

</div>

Thank you everyone, I’ve updated the PEP as suggested here and submitted it to the SC for consideration.

---

<div class="post-metadata">

**Author:** ![DanielNoord](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/danielnoord/32/8787_2.png) [@DanielNoord](https://discuss.python.org/u/DanielNoord)\
**Post date:** [November 20, 2024, 5:37pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/66 "2024-11-20T17:37:52Z")

</div>

I’m not sure whether to reply to @steve.dower here or to their comment on the Github issue in the steering council repository. Doing so here as that seems most visible to all participants.

I’m specifically responding to:

> This is a good candidate for linters to warn about. Not for the compiler in the runtime.

`pylint` has been warning about this for more than a year (see release [notes](https://github.com/pylint-dev/pylint/releases/tag/v3.0.0) of March 2023). `ruff` also has this as a [message](https://docs.astral.sh/ruff/rules/return-in-try-except-finally/) although I’m not familiar enough with the project to determine since when.

One of the projects that Irit found to be incorrectly using [this pattern](https://github.com/bluesky/bluesky/issues/1838), actually uses `ruff` but still didn’t get the warning as they hadn’t enabled this particular message. I think this is a perfect example of why saying that linters should do this check doesn’t completely solve the issue. Other issues, besides linters implementing the check but users not turning on the check, include linters being out of date, linters being poorly implemented, etc etc.  
I think the research by Irit and others clearly establishes that 1) the pattern is often misunderstood and 2) can be easily replaced by different patterns. Turning this into `SyntaxWarning` seems sensible and useful to me. It will help users prevent these issues, whereas the other proposed solution (ie., let linters do this) is clearly suboptimal.

_I just randomly picked this example by looking at Irit’s Github profile, checking their recently opened issues and checking whether this project used a linter. This was the first issue I opened. The research doesn’t include a full list of projects misusing the pattern, or I couldn’t find it, so I can’t really do a full analysis on linter usage._

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [November 20, 2024, 5:55pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/67 "2024-11-20T17:55:45Z")

</div>

> [@DanielNoord](#):
>
> I’m not sure whether to reply to @steve.dower here or to their comment on the Github issue in the steering council repository. Doing so here as that seems most visible to all participants.

Thanks, yes, this is a more appropriate place. My comment on the issue is specifically for the Steering Council, in case they don’t take the time to review the thread (and so assume that there’s complete consensus). I’m sure they’ll come and read this as a result, unless they have a strong reaction of their own to the PEP text, so your comment will be seen.

> [@DanielNoord](#):
>
> One of the projects that Irit found to be incorrectly using [this pattern](https://github.com/bluesky/bluesky/issues/1838), actually uses `ruff` but still didn’t get the warning as they hadn’t enabled this particular message.

Which is a good argument for suggesting the message be on by default, but not, IMHO, a reason to bypass linters entirely and do it ourselves.

There’s certainly enough consensus here to go to ruff on behalf of the core team and say that this deserves an on-by-default warning.

---

<div class="post-metadata">

**Author:** ![DanielNoord](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/danielnoord/32/8787_2.png) [@DanielNoord](https://discuss.python.org/u/DanielNoord)\
**Post date:** [November 20, 2024, 6:13pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/68 "2024-11-20T18:13:11Z")

</div>

> Which is a good argument for suggesting the message be on by default, but not, IMHO, a reason to bypass linters entirely and do it ourselves.

I would generally agree, but just don’t really see what problem this pattern is solving. For me that is one of the most important conclusions of Irit’s research: all “valid” patterns are replaced with a trivial other pattern that avoids the pitfalls of the current one.

> There’s certainly enough consensus here to go to ruff on behalf of the core team and say that this deserves an on-by-default warning.

At my day job we turn off all linter messages and only turn them on when somebody wants to spend the time to fix all issues, which you can imagine is almost never. I think it would still be good to indicate to `ruff` that this is a very useful message, but again that would likely not reach all audiences. (In fear of pinging them and involving them in a discussion they don’t want to get involved in, @charliermarsh this might be relevant for you and your team).

I’m not sure you can answer this, but would there be something that could convince you to support this proposal? For me the evidence of misuse and possibility of replacing it with a (in my opinion) clearer pattern is enough, but I’m wondering if there is other evidence that is currently lacking that could convince you.

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [November 20, 2024, 6:33pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/69 "2024-11-20T18:33:15Z")

</div>

> [@DanielNoord](#):
>
> For me the evidence of misuse and possibility of replacing it with a (in my opinion) clearer pattern is enough, but I’m wondering if there is other evidence that is currently lacking that could convince you.

I don’t have any problem with the evidence, it’s just the required action. Python has many millions of users and billions of lines of code - there’s rarely a percentage small enough to justify as “this won’t affect anyone”. So we should assume that people will be affected.

We know that compiler warnings have an outsized impact on users over maintainers, which is why DeprecationWarnings are off by default. So we should assume that primarily innocent users will be affected by a SyntaxWarning.

The _approach_ that would convince me to support it is to create a new warning type and have it be off by default. But that wouldn’t actually convince me because there’s no way that’s worth the cost (which is a different reason), and I believe nobody else involved would prefer that way for the same reason.

But if there’s another approach that doesn’t bother users, that doesn’t bother maintainers who are using it correctly, and doesn’t bother anyone who chooses to suppress the bother, then I’m fine with it.

---

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [November 20, 2024, 6:41pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/70 "2024-11-20T18:41:37Z")

</div>

> [@DanielNoord](#):
>
> The research doesn’t include a full list of projects misusing the pattern, or I couldn’t find it, so I can’t really do a full analysis on linter usage.

I didn’t publish the full list because I think it would turn the focus of the report from “a problem with the language” to “a shaming list of broken libraries”. I did publish the script I used to produce the list, so it is possible to replicate. I can also share my spreadsheet privately if anyone needs it for research purposes.

---

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [November 20, 2024, 7:52pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/71 "2024-11-20T19:52:55Z")

</div>

You can easily avoid that warning by overwriting the code

```python
try:
    ...
finally:
    # some code

```

as

```python
try:
    ...
except:
    # some code
    raise
else:
    # some code

```

This is an equivalent code and it has the same flaws as the original code if the cleanup code contains unconditional **return** / **break** / **continue**. And now you have a chance to take a fresh look at that code and rewrite it in a way that makes more sense. You can now have different code for **except** and **else** causes.

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [November 20, 2024, 8:24pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/72 "2024-11-20T20:24:12Z")

</div>

> [@storchaka](#):
>
> You can easily avoid that warning by overwriting the code

A user cannot. You’re making the argument for warning the maintainer via a linter, not for warning the end user via a compiler warning.

---

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [November 20, 2024, 8:38pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/73 "2024-11-20T20:38:51Z")

</div>

> [@steve.dower](#):
>
> > [@storchaka](#):
> >
> > You can easily avoid that warning by overwriting the code
> 
> A user cannot. You’re making the argument for warning the maintainer via a linter, not for warning the end user via a compiler warning.

Why is it important to avoid the warning? We went for warning rather than error so that the program will continue to run. Is that not what `SyntaxWarning` is for?

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [November 20, 2024, 9:09pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/74 "2024-11-20T21:09:03Z")

</div>

> [@iritkatriel](#):
>
> Why is it important to avoid the warning?

[PEP 565](https://peps.python.org/pep-0565/) has some discussion, and links to the earlier threads when `DeprecationWarning` was deemed too noisy for all the same reasons that this warning would be - it requires the library maintainer to fix it, not the end user.

As far as I’m aware, the only thing that’s changed since then is we probably have 10x _more_ regular users who are simply going to learn to ignore warnings.

I haven’t looked it up, but I _believe_ `SyntaxWarning` was added to warn about invalid escapes in string literals, which are far more likely to be caused by an interactive user. `return` in `finally` isn’t going to be written by an interactive user in any way that really matters - they’ll notice if it didn’t work, even if they don’t see an error (though life would be improved if they did see it).

So the issue is the warning is poorly targeted, and will be shown to the wrong people.

---

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [November 20, 2024, 9:16pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/75 "2024-11-20T21:16:45Z")

</div>

> [@steve.dower](#):
>
> `return` in `finally` isn’t going to be written by an interactive user

Like `assert` with `()`.

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [November 20, 2024, 9:31pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/76 "2024-11-20T21:31:29Z")

</div>

> [@iritkatriel](#):
>
> Like `assert` with `()`.

I’d say that `assert()` is dramatically more likely to be written by an interactive user.

It also demonstrably has zero correctness (other than as a no-op), whereas `try/finally/return` is entirely valid and just has an unfortunate side-effect that may matter in exceptional cases.

I think it’s a huge stretch to argue that all uses of `try/finally/return` are broken _enough_ to warn the users who didn’t write it and aren’t able to fix it. Appealing to existing `SyntaxWarning` cases isn’t going to help convince me (the other cases are calling literals and slicing literals with other literals instead of numbers, which can both be pretty easily caused by missing a comma).

---

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [November 20, 2024, 9:37pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/77 "2024-11-20T21:37:11Z")

</div>

> [@steve.dower](#):
>
> `try/finally/return` is entirely valid and just has an unfortunate side-effect that may matter in exceptional cases.
> 
> I think it’s a huge stretch to argue that all uses of `try/finally/return` are broken _enough_

This is where we disagree.

My analysis showed that _most_ of the return-in-finally are incorrect.  
The consequence is a swallowed exception, which is pretty bad and often hard to discover and debug. So I don’t see the harm as minor.

I do, on the other hand, consider the churn pretty small, given the overall number of instances is small (2-4 per million LOC).

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [November 20, 2024, 9:43pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/78 "2024-11-20T21:43:20Z")

</div>

> [@iritkatriel](#):
>
> This is where we disagree.

Clearly 🙂

> [@iritkatriel](#):
>
> My analysis showed that _most_ of the return-in-finally are incorrect.

Most is not all. And “incorrect” is not “broken” - by “incorrect” you mean “would swallow an exception”, which while annoying, does not affect the success case when no exception is thrown.

> [@iritkatriel](#):
>
> The consequence is a swallowed exception, which is pretty bad and often hard to discover and debug. So I don’t see the harm as minor.

It’s easy for a maintainer to run a linter and discover the risk, and then make a fix.

> [@iritkatriel](#):
>
> I do, on the other hand, consider the churn pretty small, given the overall number of instances is small (2-4 per million LOC).

I see the small number of instances as an argument in favour of us not needing to make such a big deal out of it. The amount of _fixes_ can only be “pretty small” - it’s not worth permanently adding code into our compiler, impacting every single .py file that’s ever loaded for the rest of time, for the sake of 2-4 cases per million LOC.

It’s not worth training users that they should just ignore any printed output when they `import <third-party module>` in Python/Jupyter/etc, for the sake of 2-4 cases per million LOC.

It’s not worth breaking test suites that run with warnings-as-errors because one of their dependencies is slow to make a fix, for the sake of 2-4 cases per million LOC.

---

<div class="post-metadata">

**Author:** ![malemburg](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/malemburg/32/50_2.png) [@malemburg](https://discuss.python.org/u/malemburg)\
**Post date:** [November 20, 2024, 9:49pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/79 "2024-11-20T21:49:38Z")

</div>

I’m with Steve on this one.

The compiler should not become a Python linter for things which may not be intended (but are valid Python code).

In fact, I think we should be working together with linter makers to move more such warnings, including deprecation warnings which can be detected at compile time to linters. Linters simply receive more visibility for these things by developers, while avoiding the negative impact on users.

---

<div class="post-metadata">

**Author:** ![iritkatriel](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/iritkatriel/32/2615_2.png) [@iritkatriel](https://discuss.python.org/u/iritkatriel)\
**Post date:** [November 20, 2024, 9:52pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/80 "2024-11-20T21:52:11Z")

</div>

> [@steve.dower](#):
>
> Most is not all. And “incorrect” is not “broken” - by “incorrect” you mean “would swallow an exception”, which while annoying, does not affect the success case when no exception is thrown.

That’s another point on which we disagree - I don’t see a swallowed exception as “annoying”, but as a really serious problem. That it only happens in an abnormal case (and is therefore likely to slip through testing) makes it worse.

---

<div class="post-metadata">

**Author:** ![Nineteendo](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/nineteendo/32/19122_2.png) [@Nineteendo](https://discuss.python.org/u/Nineteendo)\
**Post date:** [November 20, 2024, 9:55pm UTC](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348/81 "2024-11-20T21:55:47Z")

</div>

> [@steve.dower](#):
>
> The _approach_ that would convince me to support it is to create a new warning type and have it be off by default.

Can’t we just hide syntax warnings outside of ` __main__ ` like `DeprecationWarning`?

```python
# file.py
import warnings
def foo():
    warnings.warn("msg", DeprecationWarning, stacklevel=2)
    return "\p"
# file2.py
import file
file.foo()
# file3.py
import file2

```

```shell
$ python file.py
/tmp/file.py:6: SyntaxWarning: invalid escape sequence '\p'
$ python file2.py
/tmp/file.py:6: SyntaxWarning: invalid escape sequence '\p'
/tmp/file2.py:3: DeprecationWarning: msg
$ python file3.py
/tmp/file.py:6: SyntaxWarning: invalid escape sequence '\p'
```

[Previous page](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348.md?page=3)

[Next page](https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348.md?page=5)
