Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 758 – Allow except and except* expressions without parentheses

Author:
Pablo Galindo <pablogsal at python.org>, Brett Cannon <brett at python.org>
PEP-Delegate:
TBD
Status:
Draft
Type:
Standards Track
Created:
30-Sep-2024
Python-Version:
3.14
Post-History:
02-Oct-2024

Table of Contents

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:

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:

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:

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:

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:

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:

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:

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:

# 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:
    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


Source: https://github.com/python/peps/blob/main/peps/pep-0758.rst

Last modified: 2024-10-19 19:41:51 GMT