2
2
3
3
## Disclaimer
4
4
5
- * We use the ` ExceptionGroup ` name herein , even though there
6
- are other alternatives, e.g. ` AggregateException ` . Naming of the
7
- "exception group" object is out of scope of this proposal.
5
+ * We use the ` ExceptionGroup ` name, even though there
6
+ are other alternatives, e.g. ` AggregateException ` and ` MultiError ` .
7
+ Naming of the "exception group" object is out of scope of this proposal.
8
8
9
9
* We use the term "naked" exception for regular Python exceptions
10
10
** not wrapped** in an ` ExceptionGroup ` . E.g. a regular ` ValueError `
11
11
propagating through the stack is "naked".
12
12
13
- * We assume that ` ExceptionGroup ` would be an iterable object.
14
- E.g. ` list(ExceptionGroup(ValueError('a'), TypeError('b'))) ` would be
13
+ * ` ExceptionGroup ` is an iterable object.
14
+ E.g. ` list(ExceptionGroup(ValueError('a'), TypeError('b'))) ` is
15
15
equal to ` [ValueError('a'), TypeError('b')] `
16
16
17
- * We assume that ` ExceptionGroup ` won't be an indexable object; essentially
17
+ * ` ExceptionGroup ` is not an indexable object; essentially
18
18
it's similar to Python ` set ` . The motivation for this is that exceptions
19
19
can occur in random order, and letting users write ` group[0] ` to access the
20
20
"first" error is error prone. Although the actual implementation of
21
21
` ExceptionGroup ` will likely use an ordered list of errors to preserve
22
22
the actual occurrence order for rendering.
23
23
24
- * We assume that ` ExceptionGroup ` will be a subclass of ` BaseException ` ,
25
- which means it's assignable to ` Exception.__context__ ` and can be
24
+ * ` ExceptionGroup ` is a subclass of ` BaseException ` ,
25
+ is assignable to ` Exception.__context__ ` , and can be
26
26
directly handled with ` try: ... except ExceptionGroup: ... ` .
27
27
28
- * The behavior of the regular ` try..except ` block will not be modified.
28
+ * The behavior of the regular ` try..except ` statement will not be modified.
29
29
30
30
## Syntax
31
31
@@ -71,7 +71,7 @@ example, both `except *SpamError:` and `except *(BarError, FooError) as e:`
71
71
could get executed during handling of one ` ExceptionGroup ` object, or all
72
72
of the ` except* ` clauses, or just one of them.
73
73
74
- It is not allowed to use both regular ` except ` clauses and the new ` except* `
74
+ It is not allowed to use both regular except blocks and the new ` except* `
75
75
clauses in the same ` try ` block. E.g. the following example would raise a
76
76
` SyntaxErorr ` :
77
77
@@ -170,7 +170,7 @@ except *ValueError as e:
170
170
print (f ' got some ValueErrors: { e} ' )
171
171
except * TypeError as e:
172
172
print (f ' got some TypeErrors: { e} ' )
173
- raise e
173
+ raise
174
174
```
175
175
176
176
The above code would print:
@@ -180,7 +180,7 @@ got some ValueErrors: ExceptionGroup(ValueError('a'))
180
180
got some TypeErrors: ExceptionGroup(TypeError('b'), TypeError('c'))
181
181
```
182
182
183
- and then crash with an unhandled ` ExceptionGroup ` :
183
+ and then terminate with an unhandled ` ExceptionGroup ` :
184
184
185
185
```
186
186
ExceptionGroup(
@@ -199,15 +199,15 @@ to handle, and then:
199
199
* A new empty "result" ` ExceptionGroup ` would be created by the interpreter.
200
200
201
201
* Every ` except * ` clause, run from top to bottom, can filter some of the
202
- exceptions out of the group and process them. If the except block crashes
202
+ exceptions out of the group and process them. If the except block terminates
203
203
with an error, that error is put to the "result" ` ExceptionGroup ` (with the
204
204
group of unprocessed exceptions referenced via the ` __context__ ` attribute.)
205
205
206
206
* After there are no more ` except* ` clauses to evaluate, there are the
207
207
following possibilities:
208
208
209
209
* Both "incoming" and "result" ` ExceptionGroup ` are empty. This means
210
- that all exceptions were processed and silenced successfully .
210
+ that all exceptions were processed and silenced.
211
211
212
212
* Both "incoming" and "result" ` ExceptionGroup ` are not empty.
213
213
This means that not all of the exceptions were matched, and some were
@@ -258,7 +258,7 @@ except *ValueError:
258
258
# ZeroDivisionError()
259
259
# )
260
260
#
261
- # where the `ZeroDivizionError ()` instance would have
261
+ # where the `ZeroDivisionError ()` instance would have
262
262
# its __context__ attribute set to
263
263
#
264
264
# ExceptionGroup(
358
358
except * TypeError as e:
359
359
raise
360
360
361
- # would crash with:
361
+ # would terminate with:
362
362
#
363
363
# ExceptionGroup(
364
364
# ValueError('a'),
385
385
except * TypeError as e:
386
386
raise e
387
387
388
- # would crash with:
388
+ # would terminate with:
389
389
#
390
390
# ExceptionGroup(
391
391
# ValueError('a'),
@@ -427,10 +427,9 @@ def bar():
427
427
try :
428
428
1 / 0
429
429
except ZeroDivisionError :
430
- pass
430
+ return
431
431
finally :
432
432
print (' silence' )
433
- return
434
433
435
434
foo()
436
435
bar()
@@ -444,6 +443,36 @@ bar()
444
443
We propose to replicate this behavior in the ` except* ` syntax as it is useful
445
444
as an escape hatch when it's clear that all exceptions can be silenced.
446
445
446
+ That said, the regular try statement allows to return a value from the except
447
+ or the finally clause:
448
+
449
+ ``` python
450
+ def bar ():
451
+ try :
452
+ 1 / 0
453
+ except ZeroDivisionError :
454
+ return 42
455
+
456
+ print (bar())
457
+
458
+ # would print "42"
459
+ ```
460
+
461
+ Allowing non-None returns in ` except* ` allows to write unpredictable code,
462
+ e.g.:
463
+
464
+ ``` python
465
+ try :
466
+ raise ExceptionGroup(A(), B())
467
+ except * A:
468
+ return 1
469
+ except * B:
470
+ return 2
471
+ ```
472
+
473
+ Therefore non-None returns are disallowed in ` except* ` clauses.
474
+
475
+
447
476
## Design Considerations
448
477
449
478
### Why try..except* syntax
@@ -525,7 +554,7 @@ Which leads to the conclusion that `except *CancelledError as e` should both:
525
554
at once with one run of the code in ` except *CancelledError ` (and not
526
555
run the code for every matched individual exception.)
527
556
528
- Why "handle all exceptions at once"? Why not run the code in the ` except `
557
+ Why "handle all exceptions at once"? Why not run the code in the except
529
558
clause for every matched exception that we have in the group?
530
559
Basically because there's no need to. As we mentioned above, catching
531
560
* operation exceptions* should be done with the regular ` except KeyError `
0 commit comments