Skip to content

Commit 1202d6a

Browse files
committed
Merge remote-tracking branch 'upstream/main' into add_rejected
2 parents ba95a0d + 6083e27 commit 1202d6a

File tree

4 files changed

+171
-10
lines changed

4 files changed

+171
-10
lines changed

peps/pep-0012/pep-NNNN.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
PEP: <REQUIRED: pep number>
22
Title: <REQUIRED: pep title>
3-
Author: <REQUIRED: list of authors' real names and optionally, email addrs>
4-
Sponsor: <real name of sponsor>
5-
PEP-Delegate: <PEP delegate's real name>
3+
Author: <REQUIRED: list of authors' names and optionally, email addrs>
4+
Sponsor: <name of sponsor>
5+
PEP-Delegate: <PEP delegate's name>
66
Discussions-To: <REQUIRED: URL of current canonical discussion thread>
77
Status: <REQUIRED: Draft | Active | Accepted | Provisional | Deferred | Rejected | Withdrawn | Final | Superseded>
88
Type: <REQUIRED: Standards Track | Informational | Process>

peps/pep-0601.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Content-Type: text/x-rst
99
Created: 26-Aug-2019
1010
Python-Version: 3.8
1111
Post-History: 26-Aug-2019, 23-Sep-2019
12+
Superseded-By: 765
1213
Resolution: https://discuss.python.org/t/pep-601-forbid-return-break-continue-breaking-out-of-finally/2239/32
1314

1415
Rejection Note

peps/pep-0745.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ Actual:
3737

3838
- 3.14 development begins: Wednesday, 2024-05-08
3939
- 3.14.0 alpha 1: Tuesday, 2024-10-15
40+
- 3.14.0 alpha 2: Tuesday, 2024-11-19
4041

4142
Expected:
4243

43-
- 3.14.0 alpha 2: Tuesday, 2024-11-19
4444
- 3.14.0 alpha 3: Tuesday, 2024-12-17
4545
- 3.14.0 alpha 4: Tuesday, 2025-01-14
4646
- 3.14.0 alpha 5: Tuesday, 2025-02-11

peps/pep-0765.rst

Lines changed: 166 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Created: 15-Nov-2024
88
Python-Version: 3.14
99
Post-History: `09-Nov-2024 <https://discuss.python.org/t/an-analysis-of-return-in-finally-in-the-wild/70633>`__,
1010
`16-Nov-2024 <https://discuss.python.org/t/pep-765-disallow-return-break-continue-that-exit-a-finally-block/71348>`__,
11+
Replaces: 601
1112

1213
Abstract
1314
========
@@ -53,8 +54,8 @@ flag them as a problem.
5354
Rationale
5455
=========
5556

56-
A recent `analysis of real world code
57-
<https://github.com/iritkatriel/finally/blob/main/README.md>`_ shows that:
57+
A recent
58+
`analysis of real world code <https://github.com/iritkatriel/finally/blob/main/README.md>`__ shows that:
5859

5960
- These features are rare (2 per million LOC in the top 8,000 PyPI
6061
packages, 4 per million LOC in a random selection of packages).
@@ -64,11 +65,13 @@ A recent `analysis of real world code
6465
- Code owners are typically receptive to fixing the bugs, and
6566
find that easy to do.
6667

68+
See `the appendix <#appendix>`__ for more details.
69+
6770
This new data indicates that it would benefit Python's users if
6871
Python itself moved them away from this harmful feature.
6972

70-
One of the arguments brought up in `the PEP 601 discussion
71-
<https://discuss.python.org/t/pep-601-forbid-return-break-continue-breaking-out-of-finally/2239/24>`__
73+
One of the arguments brought up in
74+
`the PEP 601 discussion <https://discuss.python.org/t/pep-601-forbid-return-break-continue-breaking-out-of-finally/2239/24>`__
7275
was that language features should be orthogonal, and combine without
7376
context-based restrictions. However, in the meantime :pep:`654` has
7477
been implemented, and it forbids ``return``, ``break`` and ``continue``
@@ -149,8 +152,7 @@ How to Teach This
149152

150153
The change will be documented in the language spec and in the
151154
What's New documentation. The ``SyntaxWarning`` will alert users
152-
that their code needs to change. The `empirical evidence
153-
<https://github.com/iritkatriel/finally/blob/main/README.md>`__
155+
that their code needs to change. The `empirical evidence <#appendix>`__
154156
shows that the changes necessary are typically quite
155157
straightforward.
156158

@@ -196,6 +198,164 @@ without an in-flight exception, but turn into something like a bare ``raise``
196198
when there is one. It is hard to claim that the features are orthogonal if
197199
the presence of one changes the semantics of the other.
198200

201+
Appendix
202+
========
203+
204+
``return`` in ``finally`` considered harmful
205+
--------------------------------------------
206+
207+
Below is an abridged version of a
208+
`research report <https://github.com/iritkatriel/finally/commits/main/README.md>`__
209+
by Irit Katriel, which was posted on 9 Nov 2024.
210+
It describes an investigation into usage of ``return``, ``break`` and ``continue``
211+
in a ``finally`` clause in real world code, addressing the
212+
questions: Are people using it? How often are they using it incorrectly?
213+
How much churn would the proposed change create?
214+
215+
Method
216+
^^^^^^
217+
218+
The analysis is based on the 8,000 most popular PyPI packages, in terms of number
219+
of downloads in the last 30 days. They were downloaded on the 17th-18th of
220+
October, using
221+
`a script <https://github.com/faster-cpython/tools/blob/main/scripts/download_packages.py>`__
222+
written by Guido van Rossum, which in turn relies on Hugo van Kemenade's
223+
`tool <https://hugovk.github.io/top-pypi-packages/>`__ that creates a list of the
224+
most popular packages.
225+
226+
Once downloaded, a
227+
`second script <https://github.com/iritkatriel/finally/blob/main/scripts/ast_analysis.py>`__
228+
was used to construct an AST for each file, and traverse it to identify ``break``,
229+
``continue`` and ``return`` statements which are directly inside a ``finally`` block.
230+
231+
I then found the current source code for each occurrence, and categorized it. For
232+
cases where the code seems incorrect, I created an issue in the project's bug
233+
tracker. The responses to these issues are also part of the data collected in
234+
this investigation.
235+
236+
Results
237+
^^^^^^^
238+
239+
I decided not to include a list of the incorrect usages, out of concern that
240+
it would make this report look like a shaming exercise. Instead I will describe
241+
the results in general terms, but will mention that some of the problems I found
242+
appear in very popular libraries, including a cloud security application.
243+
For those so inclined, it should not be hard to replicate my analysis, as I
244+
provided links to the scripts I used in the Method section.
245+
246+
The projects examined contained a total of 120,964,221 lines of Python code,
247+
and among them the script found 203 instances of control flow instructions in a
248+
``finally`` block. Most were ``return``, a handful were ``break``, and none were
249+
``continue``. Of these:
250+
251+
- 46 are correct, and appear in tests that target this pattern as a feature (e.g.,
252+
tests for linters that detect it).
253+
- 8 seem like they could be correct - either intentionally swallowing exceptions
254+
or appearing where an active exception cannot occur. Despite being correct, it is
255+
not hard to rewrite them to avoid the bad pattern, and it would make the code
256+
clearer: deliberately swallowing exceptions can be more explicitly done with
257+
``except BaseException:``, and ``return`` which doesn't swallow exceptions can be
258+
moved after the ``finally`` block.
259+
- 149 were clearly incorrect, and can lead to unintended swallowing of exceptions.
260+
These are analyzed in the next section.
261+
262+
**The Error Cases**
263+
264+
Many of the error cases followed this pattern:
265+
266+
.. code-block::
267+
:class: bad
268+
269+
try:
270+
...
271+
except SomeSpecificError:
272+
...
273+
except Exception:
274+
logger.log(...)
275+
finally:
276+
return some_value
277+
278+
Code like this is obviously incorrect because it deliberately logs and swallows
279+
``Exception`` subclasses, while silently swallowing ``BaseExceptions``. The intention
280+
here is either to allow ``BaseExceptions`` to propagate on, or (if the author is
281+
unaware of the ``BaseException`` issue), to log and swallow all exceptions. However,
282+
even if the ``except Exception`` was changed to ``except BaseException``, this code
283+
would still have the problem that the ``finally`` block swallows all exceptions
284+
raised from within the ``except`` block, and this is probably not the intention
285+
(if it is, that can be made explicit with another ``try``-``except BaseException``).
286+
287+
Another variation on the issue found in real code looks like this:
288+
289+
.. code-block::
290+
:class: bad
291+
292+
try:
293+
...
294+
except:
295+
return NotImplemented
296+
finally:
297+
return some_value
298+
299+
Here the intention seems to be to return ``NotImplemented`` when an exception is
300+
raised, but the ``return`` in the ``finally`` block would override the one in the
301+
``except`` block.
302+
303+
.. note:: Following the
304+
`discussion <https://discuss.python.org/t/an-analysis-of-return-in-finally-in-the-wild/70633/15>`__,
305+
I repeated the analysis on a random selection of PyPI packages (to
306+
analyze code written by *average* programmers). The sample contained
307+
in total 77,398,892 lines of code with 316 instances of ``return``/``break``/``continue``
308+
in ``finally``. So about 4 instances per million lines of code.
309+
310+
**Author reactions**
311+
312+
Of the 149 incorrect instances of ``return`` or ``break`` in a ``finally`` clause,
313+
27 were out of date, in the sense that they do not appear in the main/master branch
314+
of the library, as the code has been deleted or fixed by now. The remaining 122
315+
are in 73 different packages, and I created an issue in each one to alert the
316+
authors to the problems. Within two weeks, 40 of the 73 issues received a reaction
317+
from the code maintainers:
318+
319+
- 15 issues had a PR opened to fix the problem.
320+
- 20 received reactions acknowledging the problem as one worth looking into.
321+
- 3 replied that the code is no longer maintained so this won't be fixed.
322+
- 2 closed the issue as "works as intended", one said that they intend to
323+
swallow all exceptions, but the other seemed unaware of the distinction
324+
between ``Exception`` and ``BaseException``.
325+
326+
One issue was linked to a pre-existing open issue about non-responsiveness to Ctrl-C,
327+
conjecturing a connection.
328+
329+
Two of the issue were labelled as "good first issue".
330+
331+
**The correct usages**
332+
333+
The 8 cases where the feature appears to be used correctly (in non-test code) also
334+
deserve attention. These represent the "churn" that would be caused by blocking
335+
the feature, because this is where working code will need to change. I did not
336+
contact the authors in these cases, so we need to assess the difficulty of
337+
making these changes ourselves. It is shown in
338+
`the full report <https://github.com/iritkatriel/finally/commits/main/README.md>`__,
339+
that the change required in each case is small.
340+
341+
Discussion
342+
^^^^^^^^^^
343+
344+
The first thing to note is that ``return``/``break``/``continue`` in a ``finally``
345+
block is not something we see often: 203 instance in over 120 million lines
346+
of code. This is, possibly, thanks to the linters that warn about this.
347+
348+
The second observation is that most of the usages were incorrect: 73% in our
349+
sample (149 of 203).
350+
351+
Finally, the author responses were overwhelmingly positive. Of the 40 responses
352+
received within two weeks, 35 acknowledged the issue, 15 of which also created
353+
a PR to fix it. Only two thought that the code is fine as it is, and three
354+
stated that the code is no longer maintained so they will not look into it.
355+
356+
The 8 instances where the code seems to work as intended, are not hard to
357+
rewrite.
358+
199359
Copyright
200360
=========
201361

0 commit comments

Comments
 (0)