Skip to content

Commit 668beb0

Browse files
authored
PEP 758: Allow except and except* expressions without parentheses (#4008)
1 parent 79a41d1 commit 668beb0

File tree

2 files changed

+208
-0
lines changed

2 files changed

+208
-0
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,7 @@ peps/pep-0753.rst @warsaw
638638
# ...
639639
peps/pep-0756.rst @vstinner
640640
peps/pep-0757.rst @vstinner
641+
peps/pep-0758.rst @pablogsal @brettcannon
641642
peps/pep-0789.rst @njsmith
642643
# ...
643644
peps/pep-0801.rst @warsaw

peps/pep-0758.rst

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
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

Comments
 (0)