I’m piecing together what behavior a typechecker should implement for except and except* based on the docs and the typing spec, and there’s a few things that I’m hoping to clarify here.
In 8. Errors and Exceptions — Python 3.12.3 documentation, the tutorial incorrectly says that only tuple literals are allowed in except clauses:
An except clause may name multiple exceptions as a parenthesized tuple
If this is intentional to avoid confusing beginners then never mind, but if it’s a mistake then I think the “parenthesized” should be removed.
In 8. Compound statements — Python 3.12.3 documentation, the following correctly indicates that it could be any tuple, not just a literal:
For an except clause with an expression, that expression is evaluated, and the clause matches the exception if the resulting object is “compatible” with the exception. An object is compatible with an exception if the object is the class or a non-virtual base class of the exception object, or a tuple containing an item that is the class or a non-virtual base class of the exception object.
In the except* section that immediately follows, it mentions that the type cannot be BaseExceptionGroup but refers to the matching type as singular, without mentioning whether tuples are allowed.
An except* clause must have a matching type, and this type cannot be a subclass of BaseExceptionGroup.
The typing spec doesn’t seem to have anything about exception handler typechecking in the exceptions section. (no link provided since I’m limited to 2 links per post due to being new here)
I have 3 questions that aren’t clearly answered by the docs above:
- Should except* allow tuples of types?
Mypy/pyright/runtime all allow it, so this seems allowed and maybe we should clarify the doc here.
- Should typecheckers check against types that extend BaseExceptionGroup in except*?
The runtime gives TypeError: catching ExceptionGroup with except* is not allowed. Use except instead. when executing except* ExceptionGroup:. Mypy also gives an error, but Pyright allows it.
I think this should be checked against, and maybe the docs for except* should mention the “Use except instead” part.
- If yes for #2, should the type system disallow values which could be BaseExceptionGroup (like a type variable of
type[BaseException]) in except*?
For example, something like this would pass mypy but give an error at runtime.
exception_type: type[BaseException] = BaseExceptionGroup
try:
raise ExceptionGroup("eg", [ValueError(1)])
except* exception_type:
print("foo")
This is an edge case so maybe it’s better left up to implementors to decide how conservative/best-effort they want, but I wanted to bring it up to hear everyone’s opinions.
Seems like a few things we could add/update in the language reference and/or the typing spec to make things clearer, I’m happy to contribute those updates once we all agree.