@@ -55,14 +55,15 @@ The `except *SpamError` block will be run if the `try` code raised an
5555` ExceptionGroup ` with one or more instances of ` SpamError ` . It would also be
5656triggered if a naked instance of ` SpamError ` was raised.
5757
58- The ` except *BazError as e ` block would aggregate all instances of ` BazError `
59- into a list, wrap that list into an ` ExceptionGroup ` instance, and assign
60- the resultant object to ` e ` . The type of ` e ` would be
61- ` ExceptionGroup[BazError] ` . If there was just one naked instance of
62- ` BazError ` , it would be wrapped into an ` ExceptionGroup ` and assigned to ` e ` .
63-
64- The ` except *(BarError, FooError) as e ` would aggregate all instances of
65- ` BarError ` or ` FooError ` into a list and assign that wrapped list to ` e ` .
58+ The ` except *BazError as e ` block would create an ExceptionGroup with the
59+ same nested structure and metadata (msg, cause and context) as the one
60+ raised, but containing only the instances of ` BazError ` . This ExceptionGroup
61+ is assigned to ` e ` . The type of ` e ` would be ` ExceptionGroup[BazError] ` .
62+ If there was just one naked instance of ` BazError ` , it would be wrapped
63+ into an ` ExceptionGroup ` and assigned to ` e ` .
64+
65+ The ` except *(BarError, FooError) as e ` would split out all instances of
66+ ` BarError ` or ` FooError ` into such an ExceptionGroup and assign it to ` e ` .
6667The type of ` e ` would be ` ExceptionGroup[Union[BarError, FooError]] ` .
6768
6869Even though every ` except* ` clause can be executed only once, any number of
@@ -86,6 +87,23 @@ except *CancelledError: # <- SyntaxError:
8687 pass # combining `except` and `except*` is prohibited
8788```
8889
90+ It is possible to catch the ` ExceptionGroup ` type with a plain except, but not
91+ with an ` except* ` because the latter is ambiguous:
92+
93+ ``` python
94+ try :
95+ ...
96+ except ExceptionGroup: # <- This works
97+ pass
98+
99+
100+ try :
101+ ...
102+ except * ExceptionGroup: # <- Runtime error
103+ pass
104+ ```
105+
106+
89107Exceptions are matched using a subclass check. For example:
90108
91109``` python
@@ -113,7 +131,7 @@ Example:
113131``` python
114132try :
115133 raise ExceptionGroup(
116- ValueError (' a' ), TypeError (' b' ), TypeError (' c' ), KeyError (' e' )
134+ " msg " , ValueError (' a' ), TypeError (' b' ), TypeError (' c' ), KeyError (' e' )
117135 )
118136except * ValueError as e:
119137 print (f ' got some ValueErrors: { e} ' )
@@ -125,14 +143,15 @@ except *TypeError as e:
125143The above code would print:
126144
127145```
128- got some ValueErrors: ExceptionGroup(ValueError('a'))
129- got some TypeErrors: ExceptionGroup(TypeError('b'), TypeError('c'))
146+ got some ValueErrors: ExceptionGroup("msg", ValueError('a'))
147+ got some TypeErrors: ExceptionGroup("msg", TypeError('b'), TypeError('c'))
130148```
131149
132150and then terminate with an unhandled ` ExceptionGroup ` :
133151
134152```
135153ExceptionGroup(
154+ "msg",
136155 TypeError('b'),
137156 TypeError('c'),
138157 KeyError('e'),
@@ -143,31 +162,34 @@ Basically, before interpreting `except *` clauses, the interpreter will
143162have an "incoming" ` ExceptionGroup ` object with a list of exceptions in it
144163to handle, and then:
145164
146- * A new empty "result" ` ExceptionGroup ` would be created by the interpreter.
165+ * The interpreter creates two new empty result lists for the exceptions that
166+ will be raised in the except* blocks: a "reraised" list for the naked raises
167+ and a "raised" list of the parameterised raises.
147168
148169* Every ` except * ` clause, run from top to bottom, can match a subset of the
149170 exceptions out of the group forming a "working set" of errors for the current
150- clause. If the except block raises an exception, that exception is added
151- to the "result" ` ExceptionGroup ` (with its "working set" of errors
152- linked to that exception via the ` __context__ ` attribute.)
171+ clause. These exceptions are removed from the "incoming" group. If the except
172+ block raises an exception, that exception is added to the appropriate result
173+ list ("raised" or "reraised"), and in the case of "raise" it gets its
174+ "working set" of errors linked to it via the ` __context__ ` attribute.
153175
154176* After there are no more ` except* ` clauses to evaluate, there are the
155177 following possibilities:
156178
157- * Both "incoming" and "result" ` ExceptionGroup ` s are empty. This means
158- that all exceptions were processed and silenced.
179+ * Both the "incoming" ` ExceptionGroup ` and the two result lists are empty. This
180+ means that all exceptions were processed and silenced.
181+
182+ * The "incoming" ` ExceptionGroup ` is non-empty but the result lists are:
183+ not all exceptions were processed. The interpreter raises the "incoming" group.
159184
160- * Both "incoming" and "result" ` ExceptionGroup ` s are not empty.
161- This means that not all of the exceptions were matched, and some were
162- matched but either triggered new errors, or were re-raised. The interpreter
163- would merge both groups into one group and raise it.
185+ * At least one of the result lists is non-empty: there are exceptions raised
186+ from the except* clauses. The interpreter constructs a new ` ExceptionGroup ` with
187+ an empty message and an exception list that contains all exceptions in "raised"
188+ in addition to a single ExceptionGroup which holds the exceptions in "reraised"
189+ and "incoming", in the same nested structure and with the same metadata as in
190+ the original incoming exception.
164191
165- * The "incoming" ` ExceptionGroup ` is non-empty: not all exceptions were
166- processed. The interpreter would raise the "incoming" group.
167192
168- * The "result" ` ExceptionGroup ` is non-empty: all exceptions were processed,
169- but some were re-raised or caused new errors. The interpreter would
170- raise the "result" group.
171193
172194The order of ` except* ` clauses is significant just like with the regular
173195` try..except ` , e.g.:
@@ -189,7 +211,7 @@ except *BlockingIOError:
189211
190212### Raising ExceptionGroups manually
191213
192- Exception groups can be raised manually :
214+ Exception groups can be created and raised as follows :
193215
194216``` python
195217try :
@@ -199,44 +221,52 @@ except *OSerror as errors:
199221 for e in errors:
200222 if e.errno != errno.EPIPE :
201223 new_errors.append(e)
202- raise ExceptionGroup(* new_errors)
224+ raise ExceptionGroup(errors.msg, * new_errors)
203225```
204226
205227The above code ignores all ` EPIPE ` OS errors, while letting all other
206228exceptions propagate.
207229
208- Raising an ` ExceptionGroup ` introduces nesting:
230+ Raising exceptions while handling an ` ExceptionGroup ` introduces nesting because
231+ the traceback and chaining information needs to be maintained:
209232
210233``` python
211234try :
212- raise ExceptionGroup(ValueError (' a' ), TypeError (' b' ))
235+ raise ExceptionGroup(" one " , ValueError (' a' ), TypeError (' b' ))
213236except * ValueError :
214- raise ExceptionGroup(KeyError (' x' ), KeyError (' y' ))
237+ raise ExceptionGroup(" two " , KeyError (' x' ), KeyError (' y' ))
215238
216239# would result in:
217240#
218241# ExceptionGroup(
219- # ExceptionGroup(
242+ # "",
243+ # ExceptionGroup( <-- context = ExceptionGroup(ValueError('a'))
244+ # "two",
220245# KeyError('x'),
221246# KeyError('y'),
222247# ),
223- # TypeError('b'),
248+ # ExceptionGroup( <-- context, cause, tb same as the original "one"
249+ # "one",
250+ # TypeError('b'),
251+ # )
224252# )
225253```
226254
227- Although a regular ` raise Exception ` would not wrap ` Exception ` in a group:
255+ A regular ` raise Exception ` would not wrap ` Exception ` in its own group, but a new group would still be
256+ created to merged it with the ExceptionGroup of unhandled exceptions:
228257
229258``` python
230259try :
231- raise ExceptionGroup(ValueError (' a' ), TypeError (' b' ))
260+ raise ExceptionGroup(" eg " , ValueError (' a' ), TypeError (' b' ))
232261except * ValueError :
233262 raise KeyError (' x' )
234263
235264# would result in:
236265#
237266# ExceptionGroup(
267+ # "",
238268# KeyError('x'),
239- # TypeError('b')
269+ # ExceptionGroup("eg", TypeError('b') )
240270# )
241271```
242272
@@ -248,21 +278,26 @@ referenced from the just occurred exception via its `__context__` attribute:
248278
249279``` python
250280try :
251- raise ExceptionGroup(ValueError (' a' ), ValueError (' b' ), TypeError (' z' ))
281+ raise ExceptionGroup(" eg " , ValueError (' a' ), ValueError (' b' ), TypeError (' z' ))
252282except * ValueError :
253283 1 / 0
254284
255285# would result in:
256286#
257287# ExceptionGroup(
258- # TypeError('z'),
288+ # "",
289+ # ExceptionGroup(
290+ # "eg",
291+ # TypeError('z'),
292+ # ),
259293# ZeroDivisionError()
260294# )
261295#
262296# where the `ZeroDivisionError()` instance would have
263297# its __context__ attribute set to
264298#
265299# ExceptionGroup(
300+ # "eg",
266301# ValueError('a'),
267302# ValueError('b')
268303# )
@@ -272,21 +307,25 @@ It's also possible to explicitly chain exceptions:
272307
273308``` python
274309try :
275- raise ExceptionGroup(ValueError (' a' ), ValueError (' b' ), TypeError (' z' ))
310+ raise ExceptionGroup(" eg " , ValueError (' a' ), ValueError (' b' ), TypeError (' z' ))
276311except * ValueError as errors:
277312 raise RuntimeError (' unexpected values' ) from errors
278313
279314# would result in:
280315#
281316# ExceptionGroup(
282- # TypeError('z'),
317+ # ExceptionGroup(
318+ # "eg",
319+ # TypeError('z'),
320+ # ),
283321# RuntimeError('unexpected values')
284322# )
285323#
286324# where the `RuntimeError()` instance would have
287325# its __cause__ attribute set to
288326#
289327# ExceptionGroup(
328+ # "eg",
290329# ValueError('a'),
291330# ValueError('b')
292331# )
@@ -300,21 +339,23 @@ recursively. E.g.:
300339``` python
301340try :
302341 raise ExceptionGroup(
342+ " eg" ,
303343 ValueError (' a' ),
304344 TypeError (' b' ),
305345 ExceptionGroup(
346+ " nested" ,
306347 TypeError (' c' ),
307348 KeyError (' d' )
308349 )
309350 )
310351except * TypeError as e:
311- print (f ' got some TypeErrors: { list (e) } ' )
352+ print (f ' e = { e } ' )
312353except * Exception :
313354 pass
314355
315356# would print:
316357#
317- # got some TypeErrors: [ TypeError('b'), TypeError('c')]
358+ # e = ExceptionGroup("eg", TypeError('b'), ExceptionGroup("nested", TypeError('c'))
318359```
319360
320361Iteration over an ` ExceptionGroup ` that has nested ` ExceptionGroup ` objects
@@ -324,9 +365,11 @@ in it effectively flattens the entire tree. E.g.
324365print (
325366 list (
326367 ExceptionGroup(
368+ " eg" ,
327369 ValueError (' a' ),
328370 TypeError (' b' ),
329371 ExceptionGroup(
372+ " nested" ,
330373 TypeError (' c' ),
331374 KeyError (' d' )
332375 )
@@ -349,9 +392,11 @@ likely get lost:
349392``` python
350393try :
351394 raise ExceptionGroup(
395+ " top" ,
352396 ValueError (' a' ),
353397 TypeError (' b' ),
354398 ExceptionGroup(
399+ " nested" ,
355400 TypeError (' c' ),
356401 KeyError (' d' )
357402 )
@@ -369,26 +414,33 @@ If the user wants to "flatten" the tree, they can explicitly create a new
369414``` python
370415try :
371416 raise ExceptionGroup(
417+ " one" ,
372418 ValueError (' a' ),
373419 TypeError (' b' ),
374420 ExceptionGroup(
421+ " two" ,
375422 TypeError (' c' ),
376423 KeyError (' d' )
377424 )
378425 )
379426except * TypeError as e:
380- raise ExceptionGroup(* e)
427+ raise ExceptionGroup(" three " , * e)
381428
382429# would terminate with:
383430#
384431# ExceptionGroup(
385- # ValueError('a') ,
432+ # "three" ,
386433# ExceptionGroup(
387434# TypeError('b'),
388435# TypeError('c'),
389436# ),
390437# ExceptionGroup(
391- # KeyError('d')
438+ # "one",
439+ # ValueError('a'),
440+ # ExceptionGroup(
441+ # "two",
442+ # KeyError('d')
443+ # )
392444# )
393445# )
394446```
@@ -430,9 +482,11 @@ group:
430482``` python
431483try :
432484 raise ExceptionGroup(
485+ " one" ,
433486 ValueError (' a' ),
434487 TypeError (' b' ),
435488 ExceptionGroup(
489+ " two" ,
436490 TypeError (' c' ),
437491 KeyError (' d' )
438492 )
@@ -443,9 +497,11 @@ except *TypeError as e:
443497# would both terminate with:
444498#
445499# ExceptionGroup(
500+ # "one",
446501# ValueError('a'),
447502# TypeError('b'),
448503# ExceptionGroup(
504+ # "two",
449505# TypeError('c'),
450506# KeyError('d')
451507# )
@@ -462,7 +518,7 @@ Consider if they were allowed:
462518``` python
463519def foo ():
464520 try :
465- raise ExceptionGroup(A(), B())
521+ raise ExceptionGroup(" msg " , A(), B())
466522 except * A:
467523 return 1
468524 except * B:
0 commit comments