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 ========== .. [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 Copyright ========= This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.