diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 36b5fbed6..2c99583b4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -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 diff --git a/peps/pep-0758.rst b/peps/pep-0758.rst new file mode 100644 index 000000000..df3efa0e0 --- /dev/null +++ b/peps/pep-0758.rst @@ -0,0 +1,207 @@ +PEP: 758 +Title: Allow ``except`` and ``except*`` expressions without parentheses +Author: Pablo Galindo , Brett Cannon +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.