|
| 1 | +PEP: 758 |
| 2 | +Title: Allow ``except`` and ``except*`` expressions without parentheses |
| 3 | +Author: Pablo Galindo < [email protected]>, Brett Cannon < [email protected]> |
| 4 | +PEP-Delegate: TBD |
| 5 | +Status: Draft |
| 6 | +Type: Standards Track |
| 7 | +Created: 30-Sep-2024 |
| 8 | +Python-Version: 3.14 |
| 9 | + |
| 10 | + |
| 11 | +Abstract |
| 12 | +======== |
| 13 | + |
| 14 | +This PEP [1]_ proposes to allow unparenthesized ``except`` and ``except*`` |
| 15 | +blocks in Python's exception handling syntax. Currently, when catching multiple |
| 16 | +exceptions, parentheses are required around the exception types. This was a |
| 17 | +Python 2 remnant. This PEP suggests allowing the omission of these parentheses, |
| 18 | +simplifying the syntax, making it more consistent with other parts of the syntax |
| 19 | +that make parentheses optional, and improving readability in certain cases. |
| 20 | + |
| 21 | + |
| 22 | +Motivation |
| 23 | +========== |
| 24 | + |
| 25 | +The current syntax for catching multiple exceptions requires parentheses in the |
| 26 | +``except`` expression (equivalently for the ``except*`` expression). For |
| 27 | +example: |
| 28 | + |
| 29 | +.. code-block:: python |
| 30 | +
|
| 31 | + try: |
| 32 | + ... |
| 33 | + except (ExceptionA, ExceptionB, ExceptionC): |
| 34 | + ... |
| 35 | +
|
| 36 | +While this syntax is clear and unambiguous, it can be seen as unnecessarily |
| 37 | +verbose in some cases, especially when catching a large number of exceptions. By |
| 38 | +allowing the omission of parentheses, we can simplify the syntax: |
| 39 | + |
| 40 | +.. code-block:: python |
| 41 | +
|
| 42 | + try: |
| 43 | + ... |
| 44 | + except ExceptionA, ExceptionB, ExceptionC: |
| 45 | + ... |
| 46 | +
|
| 47 | +This change would bring the syntax more in line with other comma-separated lists |
| 48 | +in Python, such as function arguments, generator expressions inside of a |
| 49 | +function call, and tuple literals, where parentheses are optional. |
| 50 | + |
| 51 | +The same change would apply to ``except*`` expressions. For example: |
| 52 | + |
| 53 | +.. code-block:: python |
| 54 | +
|
| 55 | + try: |
| 56 | + ... |
| 57 | + except* ExceptionA, ExceptionB, ExceptionC: |
| 58 | + ... |
| 59 | +
|
| 60 | +Both forms will also allow the use of the ``as`` clause to capture the exception |
| 61 | +instance as before: |
| 62 | + |
| 63 | +.. code-block:: python |
| 64 | +
|
| 65 | + try: |
| 66 | + ... |
| 67 | + except ExceptionA, ExceptionB, ExceptionC as e: |
| 68 | + ... |
| 69 | +
|
| 70 | +
|
| 71 | +Rationale |
| 72 | +========= |
| 73 | + |
| 74 | +The decision to allow unparenthesized ``except`` blocks is based on the |
| 75 | +following considerations: |
| 76 | + |
| 77 | +1. Simplicity: Removing the requirement for parentheses simplifies the syntax, |
| 78 | +making it more consistent with other parts of the language. |
| 79 | + |
| 80 | +2. Readability: In cases where many exceptions are being caught, the removal of |
| 81 | +parentheses can improve readability by reducing visual clutter. |
| 82 | + |
| 83 | +3. Consistency: This change makes the ``except`` clause more consistent with other parts of Python where unambiguous, comma-separated lists don't require parentheses. |
| 84 | + |
| 85 | +Specification |
| 86 | +============= |
| 87 | + |
| 88 | +The syntax for the except clause will be modified to allow an unparenthesized |
| 89 | +list of exception types. The grammar will be updated as follows: |
| 90 | + |
| 91 | +.. code-block:: peg |
| 92 | +
|
| 93 | + except_block[excepthandler_ty]: |
| 94 | + | invalid_except_stmt_indent |
| 95 | + | 'except' e=expressions t=['as' z=NAME { z }] ':' b=block { |
| 96 | + _PyAST_ExceptHandler(e, (t) ? ((expr_ty) t)->v.Name.id : NULL, b, EXTRA) } |
| 97 | + | 'except' ':' b=block { _PyAST_ExceptHandler(NULL, NULL, b, EXTRA) } |
| 98 | + | invalid_except_stmt |
| 99 | + except_star_block[excepthandler_ty]: |
| 100 | + | invalid_except_star_stmt_indent |
| 101 | + | 'except' '*' e=expressions t=['as' z=NAME { z }] ':' b=block { |
| 102 | + _PyAST_ExceptHandler(e, (t) ? ((expr_ty) t)->v.Name.id : NULL, b, EXTRA) } |
| 103 | + | invalid_except_star_stmt |
| 104 | +
|
| 105 | +
|
| 106 | +This allows both the current parenthesized syntax and the new unparenthesized |
| 107 | +syntax: |
| 108 | + |
| 109 | +.. code-block:: python |
| 110 | +
|
| 111 | + try: |
| 112 | + ... |
| 113 | + except (ExceptionA, ExceptionB): # Still valid |
| 114 | + ... |
| 115 | + except ExceptionC, ExceptionD: # New syntax |
| 116 | + ... |
| 117 | +
|
| 118 | +The semantics of exception handling remain unchanged. The interpreter will catch |
| 119 | +any of the listed exceptions, regardless of whether they are parenthesized or |
| 120 | +not. |
| 121 | + |
| 122 | + |
| 123 | +Backwards Compatibility |
| 124 | +======================= |
| 125 | + |
| 126 | +This change is fully backwards compatible. All existing code using parenthesized |
| 127 | +``except`` and ``except*`` blocks will continue to work without modification. |
| 128 | +The new syntax is purely additive and does not break any existing code. |
| 129 | + |
| 130 | +It's worth noting that in Python 2 the unparenthesized syntax was allowed with |
| 131 | +two elements, but had different semantics, in which the first element of the |
| 132 | +list was used as the exception type and the second element as the capture |
| 133 | +variable. This change does not reintroduce the Python 2 semantics, and the |
| 134 | +unparenthesized syntax will behave identically to the parenthesized version. |
| 135 | + |
| 136 | + |
| 137 | +Security Implications |
| 138 | +===================== |
| 139 | + |
| 140 | +There are no known security implications for this change. The semantics of |
| 141 | +exception handling remain the same, and this is purely a syntactic change. |
| 142 | + |
| 143 | + |
| 144 | +How to Teach This |
| 145 | +================= |
| 146 | + |
| 147 | +For new Python users, the unparenthesized syntax can be taught as the standard |
| 148 | +way to catch multiple exceptions: |
| 149 | + |
| 150 | +.. code-block:: python |
| 151 | +
|
| 152 | + try: |
| 153 | + risky_operation() |
| 154 | + except ValueError, TypeError, OSError: |
| 155 | + handle_errors() |
| 156 | +
|
| 157 | +For experienced users, it can be introduced as a new, optional syntax that can |
| 158 | +be used interchangeably with the parenthesized version. Documentation should |
| 159 | +note that both forms are equivalent: |
| 160 | + |
| 161 | +.. code-block:: python |
| 162 | +
|
| 163 | + # These are equivalent: |
| 164 | + except (ValueError, TypeError): |
| 165 | + ... |
| 166 | +
|
| 167 | + except ValueError, TypeError: |
| 168 | + ... |
| 169 | +
|
| 170 | +It should be emphasized that this is purely a syntactic change and does not |
| 171 | +affect the behaviour of exception handling. |
| 172 | + |
| 173 | + |
| 174 | +Reference Implementation |
| 175 | +======================== |
| 176 | + |
| 177 | +A proof-of-concept implementation is available at |
| 178 | +https://github.com/pablogsal/cpython/commits/notuples/. This implementation |
| 179 | +modifies the Python parser to accept the new syntax and ensures that it behaves |
| 180 | +identically to the parenthesized version. |
| 181 | + |
| 182 | + |
| 183 | +Rejected Ideas |
| 184 | +============== |
| 185 | + |
| 186 | +1. Allowing mixed parenthesized and unparenthesized syntax: |
| 187 | + |
| 188 | + .. code-block:: python |
| 189 | +
|
| 190 | + try: |
| 191 | + ... |
| 192 | + except (ValueError, TypeError), OSError: |
| 193 | + ... |
| 194 | +
|
| 195 | + This was rejected due to the potential for confusion and to maintain a clear |
| 196 | + distinction between the two styles. |
| 197 | + |
| 198 | +Footnotes |
| 199 | +========= |
| 200 | + |
| 201 | +.. [1] Originally named "Parenthetically Speaking, We Don't Need 'Em" |
| 202 | +
|
| 203 | +Copyright |
| 204 | +========= |
| 205 | + |
| 206 | +This document is placed in the public domain or under the |
| 207 | +CC0-1.0-Universal license, whichever is more permissive. |
0 commit comments