PEP 765: Disallow return/break/continue that exit a finally block

And even for pip it’s an open question whether to change the behavior to not compile by default: Switch default pip install compile option to `False` · Issue #12920 · pypa/pip · GitHub

You’re assuming that relying on pyc generation (“compile time”) is the only way to produce the warning. I’m suggesting that we don’t rely on that at all, and use e.g. -X dev or some other mechanism to enable these warnings (or even -m pylint ... or -m ruff ... :wink: )

2 Likes

But isn’t the problem with this example rather that it uses a bare except, which is discrecommended as per PEP 8? If you wrote except BaseException instead, the potential risk of masking special control-flow exceptions would at least be somewhat more obvious. finally, on the other hand, can never discriminate by the cause of the unwinding, so we cannot prevent users from unintentional exception chaining/masking there.

That being said, I do understand the analogy and thank you for the point. (FWIW, in Smalltalk, the stack is only unwound after handling an exception, active handler blocks get their own stack frame, and any new exception raised from a handler block just appears on top of the previous stack without implicit chaining/conflating. But that’s a minority approach…)

1 Like

Agreed, changing the opt-in/opt-out mechanism was the main ergonomic improvement that occurred to me, too:

I have to say, this change is directly impacting several of my projects in ways that are frustrating to deal with.

We have code that intentionally uses return in finally blocks for cleanup-with-override patterns - specifically in cases where we want to ensure certain resources are released regardless of exceptions, but we also want to signal success by returning a specific value. The code works correctly, has been in production for years without issues, and now suddenly triggers warnings that break our CI pipeline.

The real problem is that our CI runs with -Werror to catch legitimate issues early, but the standard warning filter mechanisms don’t work properly for SyntaxWarnings due to the module location information being incorrect. So we can’t selectively suppress this warning the way we normally would. Our options are:

  1. Rewrite working code that wasn’t broken

  2. Disable -Werror entirely and potentially miss real problems

  3. Add brittle workarounds to our CI configuration that bypass Python’s own warning system

None of these are good options. We’re being forced to churn stable, tested code not because it has bugs, but because the language has decided retroactively that our correct usage of a documented feature is “suspicious.”

I appreciate the research that went into this, but the practical impact on downstream projects feels like it wasn’t fully thought through. This is especially frustrating because the PEP doesn’t even commit to making this a SyntaxError eventually - we’re just stuck with a permanent warning for using the language as designed.

9 Likes

At the end of the day the language needs a way to churn off footguns, otherwise at the end of the next 30 years the language will be unusable without elaborate linter setups. The data presented in the PEP makes an overwhelming case for this mechanism to be considered a footgun.

You mention you have code “running in production”. I’m sure you’re already using linters. When you enable a linter rule you often have to rewrite code that is technically correct into something deemed an improvement, for one reason or another. So this shouldn’t be a huge burden, especially when you realize this change is for the sake of long term language health.

11 Likes

Normally, I agree with such sentiment. We should be able to make meaningful improvements and make things better over time; However, that isn’t what is even happening here. This change was done while specifically not planning to forbid this in the future or change the semantics of the construct to something people deemed less surprising, and it was done in a way that breaks people’s existing reasonable expectations, like that warning filters provided by the language will work as documented.

6 Likes

Are you classifying precompilation of the affected code in CI as being in the third category?

Edit: it occurred to me that I should also ensure folks are aware that it’s entirely possible to turn off just syntax warnings, while leaving other warnings alone:

With all warnings as errors:

$ python3 -Werror -c "1 is 1; import warnings as w; w.warn('?')"
  File "<string>", line 1
SyntaxError: "is" with 'int' literal. Did you mean "=="?
$ PYTHONWARNINGS=error python3 -c "1 is 1; import warnings as w; w.warn('?')"
  File "<string>", line 1
SyntaxError: "is" with 'int' literal. Did you mean "=="?

With specifically syntax warnings switched off:

$ python3 -Werror -W ignore::SyntaxWarning -c "1 is 1; import warnings as w; w.warn('?')"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
    1 is 1; import warnings as w; w.warn('?')
                                  ~~~~~~^^^^^
UserWarning: ?
$ PYTHONWARNINGS=error,ignore::SyntaxWarning python3 -c "1 is 1; import warnings as w; w.warn('?')"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
    1 is 1; import warnings as w; w.warn('?')
                                  ~~~~~~^^^^^
UserWarning: ?

The relative timing issues discussed upthread relate to runtime manipulation of the warnings filter (such as that performed by test runners), not to configuring the filter at interpreter startup via the -W CLI option or the PYTHONWARNINGS environment variable.

It’s likely worth calling this out explicitly in the What’s New doc.

Edit 2: PR posted with a suggested What’s New update

5 Likes

This isn’t an appropriate setting for those using warnings as errors. The problem that exists is that you can’t correctly specify the module/line number to ignore a warning (ie only ignore instances of the warning you know are intentional) with the syntax warnings currently.
There’s a PR open for this (And it was mentioned and linked upthread as well): https://github.com/python/cpython/issues/135801

Disabling all syntax warnings just to not deal with that being broken supresses things people should want to see warnings for, including your example with an identity comparison of a literal, which is much less ambiguously erroneous.

2 Likes

The Steering Council reaffirms that PEP 765 (Disallow return/break/continue that exit a finally block) has been accepted and will remain in Python 3.14.

First, while Python has historically allowed return, break, and continue to exit a finally block, this behavior was implicit, surprising, and often led to swallowed exceptions. PEP 765 introduces a warning for such patterns, discouraging implicit code and instead guiding developers toward explicit and predictable behavior, consistent with the Zen of Python principle that “Explicit is better than implicit.”

Second, it is only a SyntaxWarning. There is no concrete plan to escalate this to a SyntaxError. There is no impact on program execution.

Third, it is too late to roll back at this stage. The appropriate time to raise such objections was during the PEP discussion period or before the beta or release candidate phases.

That said, the Steering Council remains open to further proposals. Anyone wishing to revise or extend this work should seek consensus here, probably in a new thread, to ultimately submit a new PEP. Given that 3.14 will always have this behavior, we recommend waiting for practical data from the Python user community as a whole as to the impact of this change before doing so.

A big thank you to PEP authors @iritkatriel and @ncoghlan (and everyone else involved) for the thoughtful PEP and implementation.

Warm regards,
Donghee
on behalf of the Python Steering Council

13 Likes

For the record, we did, and were told that people wouldn’t mind being impacted.

For the future, can we instead assume that people will mind having warnings appear for otherwise correct code? So that we don’t have to have this same embarrassing situation again?

9 Likes

It’s only embarrassing if significant volumes of people in the python user community (who are not us) are blocked by the warning instead of the minority of code in question being improved.

The reason I see Syntax Warnings as a different case is because they’re a fallback for situations where no other static code analysis is being performed. When such analysis is being performed, the compile time warnings are redundant, so there’s no risk of missing information by turning them off during the dynamic test runs.

Fixing the location reporting is still desirable, of course, but I don’t expect it to make a huge difference in practice (since most test runners only support adjusting the warnings filter during test execution, not test discovery, and specifying detailed filters in the interpreter startup config is awkward)

@yicopa3177

Would you mind sharing some cases where finally-break/continue/return is used correctly?

3 Likes

Multiple of these issues were brought up in the discussion thread too prior to acceptance, including the part about it being a warning being disruptive, as well as the issue of it being some forever allowed but warning state, while not being incorrect.

And then again when it broke people testing the rc.

What more were we meant to do to convey the problems this would cause?

5 Likes

I’m honestly not sure, but now is the time to discuss it so we don’t end up in a similar situation for 3.15.

Do we need process improvements, more testing, more visibility, early feedback to the SC, more collaboration with PEP authors to ensure that the “cons” side is heard?

(This conversation should probably be in a separate thread.)

1 Like

It isn’t that the cons side wasn’t heard, it’s that our response of “Here’s how to turn the new warnings off if you don’t want them” is being met with “We don’t like those options, you should have left our use case as the standard behaviour, despite the 3:1 ratio of incorrect to correct usage in the collected data that convinced the SC the pain of adding the warning was worth incurring”.

Specifically, folks appear to want all three of the following:

  1. Have all warnings reported as errors in CI
  2. Have syntax warnings enabled in CI
  3. Never encounter a new syntax warning in CI for previously accepted code

Rather than conceding the 3rd point (as we don’t think that’s a reasonable design constraint to impose on the core development process), our answer has instead been to push back on the second point and suggest turning off syntax warnings in dynamic CI and relying on static code analysis to pick up those issues where tools have more configuration options to manage exactly which potential structural concerns are actually reported as problems.

(As far as selective filtering of syntax warnings goes, @storchaka is currently working on fixing the problem with syntax warnings being misattributed to the code running the compilation step instead of the code being compiled, but it’s looking like that may require API additions in the compilation interfaces to pass in the relevant module name, in which case it won’t be resolved in Python 3.14.x)

6 Likes

So there is irrefutable evidence that there is an issue.
But I don’t think there is conclusive evidence on the solution.

It can just as well be handled by:

  1. Leaving it to linters (most of other languages seem fine with that)
  2. Education

Take correct cases.
Find good places where this shorthand can be used correctly and usefully.
After all 25% were correct, right?
Focus on those.

Then everyone knows how to use it correctly and no one makes mistakes. I think the issue is that construct isn’t often used, thus people forget.

The only issue is that personally I haven’t seen any correct usage yet. Aren’t there really any cases where this is needed?

If it can be popularised by identifying its niche, then there would be no need to break (I would say pretty elegant) design.

And I think usage is quite simple and unambiguous (Python's try-finally is simple | Things I Share). If it would be something that would at least be occasionally useful, I don’t think there would be an issue - after all, alternative to do the same with except BaseException is much more verbose (and possibly slower?).

Why is it too late? It can be reverted in 3.14.1 or 3.14.2 as Guido suggested. It requires a new PEP where the case for that will be made.

Removing a SyntaxWarning is hardly a risky code change that we should be afraid to do in a minor version release, if that’s the right thing to do.

I would request that the SC take back this comment and encourage those who feel they were not heard in the PEP 765 discussion to submit a new PEP.

As far as I understand, the arguments against PEP 765 were essentially:

  1. There is no need for a syntax warning because people can use linters.
  2. Syntax warning is disruptive because people don’t use linters.

With these (and any other arguments) spelled out in a new PEP we can come to the right decision.

6 Likes

Not really. There were 203 instances, of which 46 were in unit test for linters (the inputs to tests for linter warnings), 8 were possibly correct and 149 were incorrect.

The 25% includes the unit test inputs. If you exclude them you get about 5%.

My report (linked from the PEP) contains links to the scripts you can use to replicate the analysis.

12 Likes