@@ -8,6 +8,7 @@ Created: 15-Nov-2024
88Python-Version: 3.14
99Post-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
1213Abstract
1314========
@@ -53,8 +54,8 @@ flag them as a problem.
5354Rationale
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+
6770This new data indicates that it would benefit Python's users if
6871Python 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 >`__
7275was that language features should be orthogonal, and combine without
7376context-based restrictions. However, in the meantime :pep: `654 ` has
7477been implemented, and it forbids ``return ``, ``break `` and ``continue ``
@@ -149,8 +152,7 @@ How to Teach This
149152
150153The change will be documented in the language spec and in the
151154What'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 >`__
154156shows that the changes necessary are typically quite
155157straightforward.
156158
@@ -196,6 +198,164 @@ without an in-flight exception, but turn into something like a bare ``raise``
196198when there is one. It is hard to claim that the features are orthogonal if
197199the 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+
199359Copyright
200360=========
201361
0 commit comments