PEP 760 – No More Bare Excepts

Ok, please vote in this friendly non-binding poll

  • I am ok with disallowing bare except blocks after N releases (to be determined)
  • I think we should always allow bare except blocks.
  • I am ok to disallow bare excepts with conditions (not who there are raises in the block, …)
0 voters

What about the 4th option - implicit raise when using bare except?

That’s likely a no go because it fundamentally goes against the users that think that the backwards incompatibility is too high (and it also changes semantics) and also goes against our initial goal to remove a potential foot-gun by forcing a explicit construct.

1 Like

Honestly, I don’t think this is reasonable at all. This is Python 2 → 3 level of breakage, so, arguably, this belongs to Python 4. And Python 4 is not happening. Hence, this should not be happening either.

But if it is happening, I’d say at least 10 years/releases.

26 Likes

I think majority of the bare except: actually in use would be with a raise, and forcing them to change is the backwards incompatibility concern.

If we do the bare except with condition/implicit raise, most of the code won’t require change.
Only the ones potentially doing the wrong thing will have to change or automatically see change in behavior.

To me, this all hinges on how many people, on being no longer able to use bare except, start scoping exceptions properly versus how many just switch to except BaseException or except Exception (and I don’t see the BaseException/Exception distinction as significant gain since in my experience, the people who don’t appreciate why misusing bare except is bad are also those who don’t appreciate the difference between assertions, exceptions, raise SystemExit() and sys.exit()).

But how do you measure that…

4 Likes

Is there any data on how much breakage this might cause?

I don’t like the suggested option of an implicit raise as that has the potential to quietly break existing code, while no bare excepts would at least loudly break existing code. (I would prefer not to break existing code in either case).

I worry that the result of this might just be projects dealing with a lot of PRs to replace except: with except BaseException: which I don’t think is a real improvement.

7 Likes

I think the answer to the “outright disallow bare except:” proposal has to be “No”: it’s too big a compatibility break for not enough practical benefit. Even though a bare except: is genuinely ambiguous in isolation, linters already complain about it for exactly that reason.

The bar here needs to be set higher, either:

  • can we do something in the compiler that linters can’t already do?
  • can we give all users the same benefits in bare except handling that linter users already get?

Nothing useful comes to mind for the first one. The “implicit re-raise” (along the same lines as finally:) technically qualifies, but I think that would be an inherently unclear change (as well as having the problem of running with different semantics on older Python versions), so I don’t think it’s an idea worth pursuing further.

The second avenue seems to have more potential (as others have already suggested earlier in the thread): disallow return, continue, break (yield and await would need to be permitted for async transaction handlers), and require an unconditional raise at the end.

For the error messages, I’d be inclined to say something like:

  • SyntaxError: expected unconditional 'raise' after bare 'except:' on line x (pointing at the end of the block. The suggested phrasing here is intentionally based on the handling of indentation errors when the suite is omitted entirely)
  • SyntaxError: 'return' not permitted after bare 'except:' on line x (pointing at the return statement)
  • (ditto for break and continue)

This would still rule out some currently valid code that reraises from a nested function, but that’s a much smaller level of potential breakage than disallowing bare except: entirely. A toy example of that:

>>> def reraise():
...     raise
...
>>> try:
...     1/0
... except:
...     reraise()
...
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

Ultimately, though, I think the simplest answer will be to decide that there are too many cases where bare except: isn’t actually wrong, so this is a problem best left to linters rather than the compiler.

21 Likes

Implicit raise in except: indeed changes semantics of existing code. But allowing finally e: to bind sys.exception() to e doesn’t.

True, but the replacement spelling (except BaseException:) isn’t the problem with removing bare except: entirely. The problem is needing to change the spelling at all.

3 Likes

I think it’s worth adding the finally e feature and promoting its use as a best practice instead of except: raise, irrespective of what is decided about PEP 760. Maybe it should be a separate PEP.

I believe that, because of the syntactic incompatibility, 5 years would be appropriate (i.e., the entire lifetime of a Python release).

1 Like

Non-trivial finally: statements can also eat exceptions, as they don’t prohibit altering the control flow via return/break/continue:

>>> def f():
...     try:
...         1/0
...     finally:
...         return
...
>>> f()
>>> def f():
...     for i in range(1):
...         try:
...             1/0
...         finally:
...             break
...
>>> f()
>>> def f():
...     for i in range(1):
...         try:
...             1/0
...         finally:
...             continue
...
>>> f()
>>>

So I don’t think finally e: (with an implicit re-raise) would be an improvement over except: raise.

2 Likes

I don’t agree.

In a way, any new syntax restriction (e.g., regarding unrecognized escape sequences) is that level of breakage, but practically the need to replace except: with except BaseException: is trivial to fix automatically.

As far as I remember my 2-to-3-switching struggles, most painful incompatibilities between Py2 and Py3 were those regarding types and data model rather than syntax.

4 Likes

I think the timeline given here is too short, but I agree with the motivation even if it is something linters can already handle and even if we do nothing special additionally, this is one of the largest footguns I’ve witnessed newer developers fall into with the language.

Making it so that it emits a warning, and then only after the first release where it logged a warning is EOL it becomes a syntax error would be fine, that’s 5 years, and requires users to have been using EOL python versions for a whole year to have not gotten the memo (At least with current cadence)

3 Likes

But if everyone just does an automatic find-and-replace with BaseException, this PEP was kind of pointless…

10 Likes

Unlikely scenario :slight_smile: – but even in such a case the PEP would not be pointless because, first of all, it will improve the language for code that will be written in the future.

2 Likes

There are always going to be users that just want the quickest thing that appears to work and don’t care if it’s the “right way”, and we can’t help those people. This pep will still help people who just overlooked the fact that certain control flow exceptions are important not to swallow unilaterally.

2 Likes

In a way, this PEP would help them, as except Exception is shorter than except BaseException :slight_smile:

1 Like

This is going to break a lot code and be another factor in preventing people from using the latest Python, for what amounts to a lint rule. Improve the lint infrastructure so that this practice can be stopped at individual companies/projects as an opt-in thing. We have to trust the user to know what they’re doing, and in this case to understand the basics of exception handling in what is already an extremely simple language to learn.

Breaking code to enforce “best practice” is a no-go.

24 Likes