PEP 601 – Forbid return/break/continue breaking out of finally
- Author:
- Damien George, Batuhan Taskaya
- Sponsor:
- Nick Coghlan
- Discussions-To:
- Discourse thread
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 26-Aug-2019
- Python-Version:
- 3.8
- Post-History:
- 26-Aug-2019, 23-Sep-2019
- Resolution:
- Discourse post
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 thefinally
suite.
Upon finding such a case it should emit the appropriate exception:
- For
continue
, aSyntaxError
(this is the current behaviour of 3.7). - For
return
/break
, aSyntaxWarning
in 3.10, and aSyntaxError
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
withinfinally
- Lib/test/test_sys_settrace.py:1346 - a test for
return
withinfinally
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.
Source: https://github.com/python/peps/blob/main/pep-0601.txt
Last modified: 2022-07-28 09:41:30 GMT