PEP: 601 Title: Forbid return/break/continue breaking out of finally
Author: Damien George, Batuhan Taskaya Sponsor: Alyssa Coghlan
Discussions-To:
https://discuss.python.org/t/pep-601-forbid-return-break-continue-breaking-out-of-finally/2239
Status: Rejected Type: Standards Track Content-Type: text/x-rst Created:
26-Aug-2019 Python-Version: 3.8 Post-History: 26-Aug-2019, 23-Sep-2019
Resolution:
https://discuss.python.org/t/pep-601-forbid-return-break-continue-breaking-out-of-finally/2239/32

Rejection Note

This PEP was rejected by the Steering Council by a vote of 4/4.

Guido's arguments for rejecting the PEP are: "it seems to me that most
languages implement this kind of construct but have style guides and/or
linters that reject it. I would support a proposal to add this to PEP
8", and "I note that the toy examples are somewhat misleading – the
functionality that may be useful is a conditional return (or break etc.)
inside a finally block.".

Abstract

This PEP proposes to forbid return, break and continue statements within
a finally suite where they would break out of the finally. Their use in
such a location silently cancels any active exception being raised
through the finally, leading to unclear code and possible bugs.

continue is currently not supported in a finally in Python 3.7 (due to
implementation issues) and the proposal is to not add support for it in
Python 3.8. For return and break the proposal is to deprecate their use
in Python 3.9, emit a compilation warning in Python 3.10 and then forbid
their use after that.

Motivation

The use of return, break and continue within a finally suite leads to
behaviour which is not at all obvious. Consider the following function:

    def foo():
        try:
            foo()
        finally:
            return

This will return cleanly (without an exception) even though it has
infinite recursion and raises an exception within the try. The reason is
that the return within the finally will silently cancel any exception
that propagates through the finally suite. Such behaviour is unexpected
and not at all obvious. This function is equivalent to:

    def foo():
        try:
            foo()
        except:
            pass
        return

break and continue have similar behaviour (they silence exceptions) if
they jump to code outside the finally suite. For example:

    def bar():
        while True:
            try:
                1 / 0
            finally:
                break

This behaviour goes against the following parts of The Zen of Python:

-   Explicit is better than implicit - exceptions are implicitly
    silenced
-   Readability counts - the intention of the code is not obvious
-   Errors should never pass silently; Unless explicitly silenced -
    exceptions are implicitly silenced

If this behaviour of silencing exceptions is really needed then the
explicit form of a try-except can be used instead, and this makes the
code clearer.

Independent to the semantics, implementing return/break/continue within
a finally suite is non-trivial as it requires to correctly track any
active exceptions at runtime (an executing finally suite may or may not
have an active exception) and cancel them as appropriate. CPython did
have a bug in this for the case of continue and so originally disallowed
it[1]. Requiring correct behaviour for return/break/continue within a
finally puts an unnecessary burden on alternative implementations of
Python.

Other languages

Java allows to return from within a finally block, but its use is
discouraged according to[2],[3],[4]. The Java compiler later on included
a linting option -Xlint:finally to warn against the use of return within
a finally block. The Eclipse editor also warns about this use.

Ruby allows return from inside ensure (Python's finally), but it should
be an explicit return. It is discouraged and handled by linters[5],[6].

Like Ruby, JavaScript also allows use of return/break/continue within a
finally but it is seen as unsafe and it is handled by eslint[7].

C# forbids the use of ending statements like return/goto/break within a
finally [8],[9].

Rationale

Since the behaviour of return/break/continue within a finally is
unclear, the pattern is rarely used, and there is a simple alternative
to writing equivalent code (which is more explicit), forbidding the
syntax is the most straightforward approach.

Specification

This is a change to the compiler, not the grammar. The compiler should
check for the following in a finally suite:

-   A return in any statement, at any level of nesting.
-   A break/continue in any statement, at any level of nesting, that
    would transfer control flow outside the finally suite.

Upon finding such a case it should emit the appropriate exception:

-   For continue, a SyntaxError (this is the current behaviour of 3.7).
-   For return/break, a SyntaxWarning in 3.10, and a SyntaxError after
    that.

For example, the following are all forbidden by this proposal:

    def f():
        try:
            pass
        finally:
            return

    def g():
        try:
            pass
        finally:
            try:
                return
            finally:
                pass

    def h():
        try:
            pass
        finally:
            try:
                pass
            finally:
                for x in range(10):
                    return

The following is still allowed because the continue doesn't escape the
finally:

    try:
        pass
    finally:
        for x in range(10):
            continue

Note that yielding from within a finally remains acceptable by this PEP
because resuming the generator will resume the finally and eventually
raise any active exceptions (so they are never silenced by yielding).

Backwards Compatibility

This is a backwards incompatible change, for return and break.

The following locations in the CPython standard library (at
v3.8.0b1-651-g7fcc2088a5) use return within finally:

-   Lib/subprocess.py:921 - the use here looks like a bug
-   Lib/multiprocessing/connection.py:316 - the use here looks
    legitimate but the intention is not clear
-   Lib/multiprocessing/connection.py:318 - the use here looks
    legitimate but the intention is not clear
-   Lib/test/test_sys_settrace.py:837 - a test for return within finally
-   Lib/test/test_sys_settrace.py:1346 - a test for return within
    finally

There are no uses of break within a finally (that break out of the
finally) in the standard library.

Security Implications

This is a simplification of the language, and removal of associated
code, so should not introduce any new paths for a security exploit.

How to Teach This

This feature is very rarely used so forbidding it will likely only
impact advanced users, not beginners and probably not any existing
teaching material. Since this is the removal of a feature teaching users
will be one by the raising of a SyntaxError if/when the forbidden
feature is used.

Reference Implementation

There is currently no reference implementation, although the way
continue is currently handled in a finally (raising a SyntaxError) can
be extended to return and break.

References

Copyright

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.

[1] https://github.com/python/cpython/issues/82011

[2] https://stackoverflow.com/questions/48088/returning-from-a-finally-block-in-java

[3] https://web.archive.org/web/20070922061412/http://weblogs.java.net/blog/staufferjames/archive/2007/06/_dont_return_in.html

[4] https://wiki.sei.cmu.edu/confluence/display/java/ERR04-J.+Do+not+complete+abruptly+from+a+finally+block

[5] https://github.com/rubocop/rubocop/issues/5949

[6] https://www.rubydoc.info/gems/rubocop/0.74.0/RuboCop/Cop/Lint/EnsureReturn

[7] https://eslint.org/docs/rules/no-unsafe-finally

[8] https://social.msdn.microsoft.com/Forums/vstudio/en-US/87faf259-3c54-4f3a-8d2b-ff82de44992f/return-statement-in-finally-block?forum=netfxbcl

[9] https://stackoverflow.com/a/5788268