222 lines
6.9 KiB
ReStructuredText
222 lines
6.9 KiB
ReStructuredText
PEP: 758
|
||
Title: Allow ``except`` and ``except*`` expressions without parentheses
|
||
Author: Pablo Galindo <pablogsal@python.org>, Brett Cannon <brett@python.org>
|
||
PEP-Delegate: TBD
|
||
Status: Draft
|
||
Type: Standards Track
|
||
Created: 30-Sep-2024
|
||
Python-Version: 3.14
|
||
Post-History: `02-Oct-2024 <https://discuss.python.org/t/66453>`__
|
||
|
||
Abstract
|
||
========
|
||
|
||
This PEP [1]_ proposes to allow unparenthesized ``except`` and ``except*``
|
||
blocks in Python's exception handling syntax only when not using the ``as``
|
||
clause. Currently, when catching multiple exceptions, parentheses are required
|
||
around the exception types. This was a Python 2 remnant. This PEP suggests allowing
|
||
the omission of these parentheses, simplifying the syntax, making it more consistent
|
||
with other parts of the syntax that make parentheses optional, and improving readability
|
||
in certain cases.
|
||
|
||
Motivation
|
||
==========
|
||
|
||
The current syntax for catching multiple exceptions requires parentheses in the
|
||
``except`` expression (equivalently for the ``except*`` expression). For
|
||
example:
|
||
|
||
.. code-block:: python
|
||
|
||
try:
|
||
...
|
||
except (ExceptionA, ExceptionB, ExceptionC):
|
||
...
|
||
|
||
While this syntax is clear and unambiguous, it can be seen as unnecessarily
|
||
verbose in some cases, especially when catching a large number of exceptions. By
|
||
allowing the omission of parentheses, we can simplify the syntax:
|
||
|
||
.. code-block:: python
|
||
|
||
try:
|
||
...
|
||
except ExceptionA, ExceptionB, ExceptionC:
|
||
...
|
||
|
||
This change would bring the syntax more in line with other comma-separated lists
|
||
in Python, such as function arguments, generator expressions inside of a
|
||
function call, and tuple literals, where parentheses are optional.
|
||
|
||
The same change would apply to ``except*`` expressions. For example:
|
||
|
||
.. code-block:: python
|
||
|
||
try:
|
||
...
|
||
except* ExceptionA, ExceptionB, ExceptionC:
|
||
...
|
||
|
||
When using the ``as`` clause to capture the exception instance paretheses must
|
||
be used as before. Some users have expressed that they would find it confusing not
|
||
to require parentheses as it would be unclear what exactly is being assigned to
|
||
the target since in other parts of the language multiple ``as`` clauses can be used
|
||
in similar situations (like in imports and context managers). This means that if
|
||
an ``as`` clause its being added to the previous example it must be done as:
|
||
|
||
.. code-block:: python
|
||
|
||
try:
|
||
...
|
||
except (ExceptionA, ExceptionB, ExceptionC) as e:
|
||
...
|
||
|
||
|
||
Rationale
|
||
=========
|
||
|
||
The decision to allow unparenthesized ``except`` blocks is based on the
|
||
following considerations:
|
||
|
||
1. Simplicity: Removing the requirement for parentheses simplifies the syntax,
|
||
making it more consistent with other parts of the language.
|
||
|
||
2. Readability: In cases where many exceptions are being caught, the removal of
|
||
parentheses can improve readability by reducing visual clutter.
|
||
|
||
3. Consistency: This change makes the ``except`` clause more consistent with
|
||
other parts of Python where unambiguous, comma-separated lists don't require
|
||
parentheses.
|
||
|
||
Specification
|
||
=============
|
||
|
||
The syntax for the except clause will be modified to allow an unparenthesized
|
||
list of exception types. The grammar will be updated as follows:
|
||
|
||
.. code-block:: peg
|
||
|
||
except_block:
|
||
| 'except' expressions ':' block
|
||
| 'except' expression 'as' NAME ':' block
|
||
| 'except' ':' block
|
||
|
||
except_star_block
|
||
| 'except' '*' expressions ':' block
|
||
| 'except' '*' expression 'as' NAME ':' block
|
||
|
||
This allows both the current parenthesized syntax and the new unparenthesized
|
||
syntax while requiring parentheses when the ``as`` keyword is used:
|
||
|
||
.. code-block:: python
|
||
|
||
try:
|
||
...
|
||
except (ExceptionA, ExceptionB): # Still valid
|
||
...
|
||
except ExceptionC, ExceptionD: # New syntax
|
||
...
|
||
except (ExceptionE, ExceptionF) as e: # Parentheses still required
|
||
...
|
||
|
||
The semantics of exception handling remain unchanged. The interpreter will catch
|
||
any of the listed exceptions, regardless of whether they are parenthesized or
|
||
not.
|
||
|
||
|
||
Backwards Compatibility
|
||
=======================
|
||
|
||
This change is fully backwards compatible. All existing code using parenthesized
|
||
``except`` and ``except*`` blocks will continue to work without modification.
|
||
The new syntax is purely additive and does not break any existing code.
|
||
|
||
It's worth noting that in Python 2 the unparenthesized syntax was allowed with
|
||
two elements, but had different semantics, in which the first element of the
|
||
list was used as the exception type and the second element as the capture
|
||
variable. This change does not reintroduce the Python 2 semantics, and the
|
||
unparenthesized syntax will behave identically to the parenthesized version.
|
||
|
||
|
||
Security Implications
|
||
=====================
|
||
|
||
There are no known security implications for this change. The semantics of
|
||
exception handling remain the same, and this is purely a syntactic change.
|
||
|
||
|
||
How to Teach This
|
||
=================
|
||
|
||
For new Python users, the unparenthesized syntax can be taught as the standard
|
||
way to catch multiple exceptions:
|
||
|
||
.. code-block:: python
|
||
|
||
try:
|
||
risky_operation()
|
||
except ValueError, TypeError, OSError:
|
||
handle_errors()
|
||
|
||
For experienced users, it can be introduced as a new, optional syntax that can
|
||
be used interchangeably with the parenthesized version. Documentation should
|
||
note that both forms are equivalent:
|
||
|
||
.. code-block:: python
|
||
|
||
# These are equivalent:
|
||
except (ValueError, TypeError):
|
||
...
|
||
|
||
except ValueError, TypeError:
|
||
...
|
||
|
||
It should be emphasized that this is purely a syntactic change and does not
|
||
affect the behaviour of exception handling.
|
||
|
||
|
||
Reference Implementation
|
||
========================
|
||
|
||
A proof-of-concept implementation is available at
|
||
https://github.com/pablogsal/cpython/commits/notuples/. This implementation
|
||
modifies the Python parser to accept the new syntax and ensures that it behaves
|
||
identically to the parenthesized version.
|
||
|
||
|
||
Rejected Ideas
|
||
==============
|
||
|
||
1. Allowing mixed parenthesized and unparenthesized syntax:
|
||
|
||
.. code-block:: python
|
||
|
||
try:
|
||
...
|
||
except (ValueError, TypeError), OSError:
|
||
...
|
||
|
||
This was rejected due to the potential for confusion and to maintain a clear
|
||
distinction between the two styles.
|
||
|
||
Deferred Ideas
|
||
==============
|
||
|
||
1. Allowing unparenthesized expressions when the ``as`` keyword is used. The reason
|
||
we have decided to defer this particular form given that there isn't clear
|
||
consensus either way and there are reasonable arguments for both positions, the safest
|
||
approach is keeping the parentheses requirement since it can be removed later if users
|
||
find the disconnect too acute, while taking it out and users deciding it was a bad idea
|
||
doesn’t make it easy to put back.
|
||
|
||
Footnotes
|
||
=========
|
||
|
||
.. [1] Originally named "Parenthetically Speaking, We Don't Need 'Em"
|
||
|
||
Copyright
|
||
=========
|
||
|
||
This document is placed in the public domain or under the
|
||
CC0-1.0-Universal license, whichever is more permissive.
|