What’s New In Python 3.11 predicts that the next Python release will introduce the concept of “exception group” and add the corresponding except* syntax extension:
The current release of Python 3.11.0 A3 does not have this feature, and the next alpha release is expected on January 3, 2022.
Abnormal group
This concept was developed to allow programs to throw/handle multiple exceptions at the same time.
BaseExceptionGroup
和 ExceptionGroup
These two new exception types can combine several unrelated exceptions and propagate together. Simply put, throwing one set of exceptions is equivalent to throwing n at once. Here’s an example:
raise ExceptionGroup('bad param', [ValueError('bad value'), TypeError('bad type')])
Copy the code
Error information is displayed as follows:
| ExceptionGroup
+-+---------------- 1 ----------------
| Exception Group Traceback (most recent call last):
| File "<stdin>", line 1.in <module>
| ExceptionGroup: bad param
+-+---------------- 1 ----------------
| ValueError: bad value
+------------------------------------
| TypeError: bad type
+------------------------------------
Copy the code
The first argument to ExceptionGroup is the message string and the second argument is the exception sequence (list, tuple, etc.). Of course, multi-layer nested sequences work as well:
raise ExceptionGroup("one", [
TypeError(1),
ExceptionGroup("two", [TypeError(2), ValueError(3)]),
ExceptionGroup("three", [OSError(4)))Copy the code
BaseExceptionGroup is inherited from the BaseException class, and ExceptionGroup is inherited from the Exception class.
ExceptionGroup.subgroup
和 ExceptionGroup.split
methods
Two new methods. Subgroup (condition) recursively filters exceptions/exception groups within exception groups based on the return value of condition.
Here’s an example:
eg = ExceptionGroup("one", [
TypeError(1),
ExceptionGroup("two", [
TypeError(2),
ValueError(3)
]),
ExceptionGroup("three", [
OSError(4)]])Copy the code
Eg. subgroup(lambda e: isinstance(e, TypeError)) returns:
ExceptionGroup("one", [
TypeError(1),
ExceptionGroup("two", [
TypeError(2)]])Copy the code
A similar method by subgroup is split(condtion). Split returns a tuple with the first value being the result of a match and the second value being the result of a mismatch.
Split (lambda e: isinstance(e, TypeError)))
(
ExceptionGroup("one", [
TypeError(1),
ExceptionGroup("two", [
TypeError(2)
])
]),
ExceptionGroup("one", [
ExceptionGroup("two", [
ValueError(3)
]),
ExceptionGroup("three", [
OSError(4)]]))Copy the code
except*
One exception group can trigger more than oneexcept*
Clause.
Each except* clause is executed at most once for all matched exceptions in a group. Each exception is either handled by the first clause that matches its type or rethrown at the end.
To get a clearer idea of the entire execution process behind try-except*, I’ll use the following code as an example:
try:
raise ExceptionGroup('msg', [FooError(1), FooError(2), BazError()])
except* SpamError:
...
except* FooError:
...
Copy the code
We throw a set of exceptions in the try clause:
ExceptionGroup("msg", [FooError(1), FooError(2), BazError()])
Copy the code
In the first except* clause, the Python interpreter initializes unhandled as this exception group and calls unhandled. Split (SpamError) to get the result:
(
None.# matched exception
ExceptionGroup("msg", [FooError(1), FooError(2), BazError()]) # Other exceptions
)
Copy the code
The first value is None, indicating that there is no match, and the except* block is not executed. Following the second except* clause, the program unhandled.split(FooError) returns:
(
ExceptionGroup('msg', [FooError(1), FooError(2)),# matched exception
ExceptionGroup('msg', [BazError()]) # Other exceptions
)
Copy the code
The first value is not None, the except* block is executed. ExceptionGroup(‘ MSG ‘, [BazError()]) assigns to unhandled.
Repeat the process until the end, and if the unhandled value is not None, rethrow the exception and print an error message.
except
和 except*
The composability of
This concept of mixing except and except* is roughly intended to have an except* T: handle only T exceptions in the exception group, and an except T: handle T exceptions (naked exceptions for short) that are not in the exception group. This idea was officially rejected as adding complexity rather than useful semantics.
This approach doesn’t make much sense in practice, but nested try-except blocks can be used if needed to achieve the same result:
try:
try:...except SomeError:
Handle naked exceptions
except* SomeError:
# Handle exception groups
Copy the code
In addition, except can catch BaseExceptionGroup and ExceptionGroup, but except* cannot (this syntax is ambiguous and is forbidden).
exceptValueError: // OK, the naked exception is caughtexceptExceptionGroup: // OK, the ExceptionGroup is capturedexcept* ValueError: // OK, catch bare exception & exception groupexceptExceptionGroup: // Error!except*: // error!Copy the code
Except * Caught bare exceptions are treated as exception groups:
try:
raise BlockingIOError
except* OSError as e:
print(repr(e))
Copy the code
ExceptionGroup("", [BlockingIOError()])
Copy the code
Why not just expandexcept
But introduced a new syntax?
The reason is simple: version compatibility issues.
-
Different types of capture.
Let’s say we had this code before:
try:...except OSError as err: iferr.errno ! = ENOENT:raise Copy the code
If the functionality of except is extended and Err is of type ExceptionGroup, accessing the Err. errno attribute will result in an error.
-
More than one except clause is executed once, but more than one except* clause can be executed multiple times.
This is a potentially disruptive change, as it currently undermines our perception that except is performed only once. If the except clause in previous versions contained non-idempotent operations (performing operations that had different results the first and second time, such as freeing resources), catastrophic problems would occur.
new__note__
attribute
BaseException adds a mutable attribute __note__ (default: None). This property can be annotated as an exception and is printed with an error message.
As for the usage, it is easier to catch the exception and add the information than print.
I have installed Python 3.11.0 A3.
try:
1 / 0
except Exception as e:
e.__note__ = "Custom message"
raise
Copy the code
Traceback (most recent call last):
File "<pyshell#12>", line 2.in <module>
1 / 0
ZeroDivisionError: division by zero
Custom message
Copy the code
The resources
- Docs.python.org/3.11/whatsn…
- www.python.org/dev/peps/pe…
- bugs.python.org/issue45607
- www.python.org/dev/peps/pe…