-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PEP 758: Allow
except
and except*
expressions without parenth…
…eses (#4008)
- Loading branch information
Showing
2 changed files
with
208 additions
and
0 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
PEP: 758 | ||
Title: Allow ``except`` and ``except*`` expressions without parentheses | ||
Author: Pablo Galindo <[email protected]>, Brett Cannon <[email protected]> | ||
PEP-Delegate: TBD | ||
Status: Draft | ||
Type: Standards Track | ||
Created: 30-Sep-2024 | ||
Python-Version: 3.14 | ||
|
||
|
||
Abstract | ||
======== | ||
|
||
This PEP [1]_ proposes to allow unparenthesized ``except`` and ``except*`` | ||
blocks in Python's exception handling syntax. 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: | ||
... | ||
Both forms will also allow the use of the ``as`` clause to capture the exception | ||
instance as before: | ||
|
||
.. 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[excepthandler_ty]: | ||
| invalid_except_stmt_indent | ||
| 'except' e=expressions t=['as' z=NAME { z }] ':' b=block { | ||
_PyAST_ExceptHandler(e, (t) ? ((expr_ty) t)->v.Name.id : NULL, b, EXTRA) } | ||
| 'except' ':' b=block { _PyAST_ExceptHandler(NULL, NULL, b, EXTRA) } | ||
| invalid_except_stmt | ||
except_star_block[excepthandler_ty]: | ||
| invalid_except_star_stmt_indent | ||
| 'except' '*' e=expressions t=['as' z=NAME { z }] ':' b=block { | ||
_PyAST_ExceptHandler(e, (t) ? ((expr_ty) t)->v.Name.id : NULL, b, EXTRA) } | ||
| invalid_except_star_stmt | ||
This allows both the current parenthesized syntax and the new unparenthesized | ||
syntax: | ||
|
||
.. code-block:: python | ||
try: | ||
... | ||
except (ExceptionA, ExceptionB): # Still valid | ||
... | ||
except ExceptionC, ExceptionD: # New syntax | ||
... | ||
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. | ||
|
||
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. |