Skip to content

Commit

Permalink
first draft
Browse files Browse the repository at this point in the history
  • Loading branch information
iritkatriel committed Nov 14, 2024
1 parent dbac24d commit 5ba270a
Showing 1 changed file with 164 additions and 0 deletions.
164 changes: 164 additions & 0 deletions peps/pep-9999.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
PEP: 9999
Title: Disallow return/break/continue that exit a finally block
Author: Alyssa Coghlan <ncoghlan at gmail.com> and Irit Katriel <irit at python.org>
Status: Draft
Type: Standards Track
Created: 14-Nov-2024
Post-History:

.. highlight:: rst

Abstract
========

This PEP proposes to withdraw support for ``return``, ``break`` and
``continue`` statements that break out of a ``finally`` block.
This was proposed in the past by :pep:`601`. The current PEP
is based on empirical evidence regarding the cost/benefit of
this change, which did not exist at the time that :pep:`601`
was rejected. It also proposes a slightly different solution
than that which was proposed by :pep:`601`.

Rationale
=========

The semantics of ``return``, ``break`` and ``continue`` in a
finally block are surprising for many developers.
The documentation [1]_ mentions that

1. If the ``finally`` clause executes a ``break``, ``continue``
or ``return`` statement, exceptions are not re-raised.
2. If a ``finally`` clause includes a ``return`` statement, the
returned value will be the one from the ``finally`` clause’s
``return`` statement, not the value from the ``try`` clause’s
``return`` statement.

Both of these behaviours cause confusion, but the first is
particularly dangerous because a swallowed exception is more
likely to slip through testing, than an incorrect return value.

In 2019, :pep:`601` proposed to change Python to emit a
``SyntaxWarning`` for a few releases and then turn it into a
``SyntaxError``. It was rejected in favour of viewing this
as a programming style issue, to be handled by linters and :pep:`8`.
Indeed, :pep:`8` now recommends not to used control flow statements
in a ``finally`` block, and linters such as pylint [2]_, ruff [3]_
and flake8-bugbear [4]_ flag them as a problem.

A recent analysis of real world code [5]_ shows that::

- These features are rare (2 per million LOC in the top 8000 PyPI
packages, 4 per million LOC in a random selection of packages).
This could be thanks to the linters that flag this pattern.
- Most of the usages are incorrect, and introduce unintended
exception-swallowing bugs.
- Code owners are typically receptive to fixing the bugs, and
find that easy to do.

This new data indicates that it would benefit Python's users if
Python itself moved them away from this harmful feature.

One of the arguments brought up [6]_ in the discussion about :pep:`601`
was that language features should be orthogonal, and combine without
context-based restrictions. However, in the meantime :pep:`654` has
been implemented, and it forbids `return`, `break` and `continue`
in an ``except*`` clause because the semantics of that would violate
the property that ``except*`` clauses operate *in parallel*, so the
code of one clause should not suppress the invocation of another.
In that case we accepted that a combination of features can be
harmful enough that it makes sense to disallow it.


Specification
=============

The change is to specify as part of the language spec that
Python's compiler may emit a ``SyntaxWarning`` or ``SyntaxError``
when a ``return``, ``break`` or ``continue`` would transfer
control flow from within a ``finally`` block to a location outside
of it.

This includes the following examples:

.. code-block::
def f():
try:
...
finally:
return 42
for x in o:
try:
...
finally:
break (or continue)
But excludes these:

.. code-block::
try:
...
finally:
def f():
return 42
try:
...
finally:
for x in o:
break (or continue)
CPython will emit a ``SyntaxWarning`` in version 3.14, and we leave
it open whether, and when, this will become a ``SyntaxError``.
However, we specify here that a ``SyntaxError`` is permitted by
the language spec, so that other Python implementations can choose
to implement that.

Backwards Compatibility
=======================

For backwards compatibility reasons, we are proposing that CPython
emit only a ``SyntaxWarning``, with no concrete plan to upgrade that
to an error. Code running with ``-We`` may stop working once this
is introduced.

Security Implications
=====================

The warning/error will help programmers avoid some hard to find bugs,
so will have a security benefit. We are not aware of security issues
related to raising a new ``SyntaxWarning`` or ``SyntaxError``.

How to Teach This
=================

The change will be documented in the language spec and in the
What's New documentation. The `SyntaxWarning` will alert users
that their code needs to change. The empirical evidence [5]_
shows that the changes necessary are typically quite
straightforward.

References
==========

.. [1] https://docs.python.org/3/tutorial/errors.html#defining-clean-up-actions
.. [2] https://pylint.readthedocs.io/en/stable/
.. [3] https://docs.astral.sh/ruff/
.. [4] https://github.com/PyCQA/flake8-bugbear
.. [5] https://github.com/iritkatriel/finally/blob/main/README.md
.. [6] https://discuss.python.org/t/pep-601-forbid-return-break-continue-breaking-out-of-finally/2239/24
Copyright
=========

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

0 comments on commit 5ba270a

Please sign in to comment.