PEP 758: Allow ``except`` and ``except*`` expressions without parentheses (#4008)
This commit is contained in:
parent
79a41d1002
commit
668beb0cde
|
@ -638,6 +638,7 @@ peps/pep-0753.rst @warsaw
|
|||
# ...
|
||||
peps/pep-0756.rst @vstinner
|
||||
peps/pep-0757.rst @vstinner
|
||||
peps/pep-0758.rst @pablogsal @brettcannon
|
||||
peps/pep-0789.rst @njsmith
|
||||
# ...
|
||||
peps/pep-0801.rst @warsaw
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
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
|
||||
|
||||
|
||||
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.
|
Loading…
Reference in New Issue