Repository navigation
Implement PEP 654: Exception Groups #89455
Description
Activity
- addeddocsDocumentation in the Doc dirDocumentation in the Doc dirinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement
on Sep 26, 2021 We will implement Exception Groups and except* in a series of PRs:
- Add the ExceptionGroup and BaseExceptionGroup classes
- Update traceback rendering code (python and C) for exception groups.
- Implement except*
- Write documentation sections.
- changed the title
[-]Implement PEP 654[/-][+]Implement PEP 654: Exception Groups[/+]on Sep 26, 2021 The tests emit some deprecation warnings :
PYTHONWARNINGS=always ./python -Wall -m test test_exception_group 0:00:00 load avg: 0.39 Run tests sequentially 0:00:00 load avg: 0.39 [1/1] test_exception_group /home/karthikeyan/stuff/python/cpython/Lib/test/test_exception_group.py:41: DeprecationWarning: invalid escape sequence '\(' MSG = 'second argument \(exceptions\) must be a sequence' /home/karthikeyan/stuff/python/cpython/Lib/test/test_exception_group.py:47: DeprecationWarning: invalid escape sequence '\(' MSG = 'second argument \(exceptions\) must be a non-empty sequence' /home/karthikeyan/stuff/python/cpython/Lib/test/test_exception_group.py:52: DeprecationWarning: invalid escape sequence '\(' MSG = ('Item [0-9]+ of second argument \(exceptions\)'
== Tests result: SUCCESS ==
1 test OK.
Total duration: 51 ms
Tests result: SUCCESSWe should have a discussion here about improvements that Mark Shannon would like to see, in particular to do more work in the compiler instead of the interpreter.
PR 29581 resulted in a 1% slowdown, which is not terrible, but code not using except* should not be slowed down at all.
IMO, the way to avoid the slowdown is to implement except* using the existing instruction set (perhaps with a few minor additions)
We already implement try-finally, named except blocks and with statements without any complex bytecodes (except perhaps WITH_EXCEPT_START).
These used to involve a lot of state and more complex bytecodes. So it is possible to make these simplifications,
but it does take work.There are a number of techniques we can use:
If any state is needed, push it to the stack as we do with
ctx.__exit__in the with statement, and when pushing f_lasti in exception handlers.
Duplicate code paths when the semantics differ in different cases, as we do for finally blocks.
If anything is too complex to handle on the stack, put it in a temporary variable.
Be liberal in your use of virtual try-excepts (SETUP_FINALLY, POP_FINALLY pairs), as zero-cost exception handling should keep the cost down.It may be too late for this advice, but if I were writing the
except*implementation from scratch, I would:-
Sketch out the pseudo Python that a try-except* would map to. This is a good opportunity to discover any design bugs that might result in undesirable behavior for corner cases.
-
Implement the translation in the compiler, not worrying about any redundancy or inefficiency, just correctness.
-
Look to improve the above, either in the compiler front-end, or by replacing inefficient code patterns in the back-end. Handling the optimization in the backend has the advantage that other code might benefit as well.
-
The PR adds two new opcodes. Let's start with the simpler of the two - JUMP_IF_NOT_EG_MATCH. This is the exception-group variation on JUMP_IF_NOT_EXC_MATCH.
JUMP_IF_NOT_EXC_MATCH checks for a match by checking if the exception is of the given type. The result is boolean.
JUMP_IF_NOT_EG_MATCH checks for a matching by calling .split() on the exception group. The result is two exception groups (the matching part and the non-matching part).
Can we do this without a new opcode?
The second opcode that the PR adds is PREP_RERAISE_STAR.
This opcode takes a list that contains:
- all the exceptions that were raised in the executed except* clauses
- the unmatched part of the exception group
It constructs the exception group that needs to be raised at the end. This is done through a fairly complex operation on the BaseExceptionGroup, which merges the re-raised exceptions into the same nesting structure they had in the original exception group, so that
try:
raise eg
except* ValueError:
raise
except* TypeError:
raiseis equivalent to just 'raise eg'.
Is there any overlap with existing opcodes?
The way these two opcodes are combined by the compiler to implement except* is described in the pseudo code here:
except* uses JUMP_IF_NOT_EG_MATCH. The excepts (not-) that collect exceptions raised in the except clauses are virtual.
The do_reraise_star at the end is PREP_RERAISE_STAR followed by POP_EXCEPT_AND_RERAISE.
- added a commit that references this issue
on May 29, 2023
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
Linked PRs