Skip to content

Implement PEP 654: Exception Groups #89455

Description

@iritkatriel
BPO 45292
Nosy @gvanrossum, @markshannon, @tirkarthi, @erlend-aasland, @iritkatriel
PRs
  • bpo-45292: [PEP 654] add the ExceptionGroup and BaseExceptionGroup classes #28569
  • bpo-45292: [PEP 654] Update traceback display code to work with exception groups #29207
  • bpo-45292: Use raw strings for regex in tests #29545
  • bpo-45292: [PEP-654] add except* #29581
  • bpo-45292: [PEP-654] exception groups and except* documentation #30158
  • 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:

    assignee = 'https://github.com/iritkatriel'
    closed_at = <Date 2022-01-06.19:06:15.397>
    created_at = <Date 2021-09-26.12:07:11.663>
    labels = ['interpreter-core', '3.11', 'type-feature', 'library', 'docs']
    title = 'Implement PEP 654: Exception Groups'
    updated_at = <Date 2022-01-06.19:06:15.396>
    user = 'https://github.com/iritkatriel'

    bugs.python.org fields:

    activity = <Date 2022-01-06.19:06:15.396>
    actor = 'iritkatriel'
    assignee = 'iritkatriel'
    closed = True
    closed_date = <Date 2022-01-06.19:06:15.397>
    closer = 'iritkatriel'
    components = ['Documentation', 'Interpreter Core', 'Library (Lib)']
    creation = <Date 2021-09-26.12:07:11.663>
    creator = 'iritkatriel'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 45292
    keywords = ['patch']
    message_count = 12.0
    messages = ['402661', '404837', '405773', '406266', '406400', '408549', '408557', '408559', '408560', '408589', '408590', '409898']
    nosy_count = 5.0
    nosy_names = ['gvanrossum', 'Mark.Shannon', 'xtreak', 'erlendaasland', 'iritkatriel']
    pr_nums = ['28569', '29207', '29545', '29581', '30158']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue45292'
    versions = ['Python 3.11']

    Linked PRs

    Activity

    1. self-assigned this
      on Sep 26, 2021
    2. added
      docsDocumentation in the Doc dir
      interpreter-core(Objects, Python, Grammar, and Parser dirs)
      stdlibStandard Library Python modules in the Lib/ directory
      type-featureA feature request or enhancement
      on Sep 26, 2021
    3. iritkatriel commented on Sep 26, 2021

      @iritkatriel
      MemberAuthor

      We will implement Exception Groups and except* in a series of PRs:

      1. Add the ExceptionGroup and BaseExceptionGroup classes
      2. Update traceback rendering code (python and C) for exception groups.
      3. Implement except*
      4. Write documentation sections.
    4. changed the title [-]Implement PEP 654[/-] [+]Implement PEP 654: Exception Groups[/+] on Sep 26, 2021
    5. iritkatriel commented on Oct 22, 2021

      @iritkatriel
      MemberAuthor

      New changeset f30ad65 by Irit Katriel in branch 'main':
      bpo-45292: [PEP-654] add the ExceptionGroup and BaseExceptionGroup classes (GH-28569)
      f30ad65

    6. iritkatriel commented on Nov 5, 2021

      @iritkatriel
      MemberAuthor

      New changeset 3509b26 by Irit Katriel in branch 'main':
      bpo-45292: [PEP-654] Update traceback display code to work with exception groups (GH-29207)
      3509b26

    7. tirkarthi commented on Nov 13, 2021

      @tirkarthi
      Member

      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: SUCCESS

    8. tirkarthi commented on Nov 16, 2021

      @tirkarthi
      Member

      New changeset 8b06d01 by Irit Katriel in branch 'main':
      bpo-45292: Use raw strings for regex in tests (GH-29545)
      8b06d01

    9. iritkatriel commented on Dec 14, 2021

      @iritkatriel
      MemberAuthor

      New changeset d60457a by Irit Katriel in branch 'main':
      bpo-45292: [PEP-654] add except* (GH-29581)
      d60457a

    10. gvanrossum commented on Dec 14, 2021

      @gvanrossum
      Member

      We 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.

    11. markshannon commented on Dec 14, 2021

      @markshannon
      Member

      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:

      1. 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.

      2. Implement the translation in the compiler, not worrying about any redundancy or inefficiency, just correctness.

      3. 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.

    12. iritkatriel commented on Dec 14, 2021

      @iritkatriel
      MemberAuthor

      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?

    13. iritkatriel commented on Dec 15, 2021

      @iritkatriel
      MemberAuthor

      The second opcode that the PR adds is PREP_RERAISE_STAR.

      This opcode takes a list that contains:

      1. all the exceptions that were raised in the executed except* clauses
      2. 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:
      raise

      is equivalent to just 'raise eg'.

      Is there any overlap with existing opcodes?

    14. iritkatriel commented on Dec 15, 2021

      @iritkatriel
      MemberAuthor

      The way these two opcodes are combined by the compiler to implement except* is described in the pseudo code here:

      #29581 (comment)

      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.

    15. iritkatriel commented on Jan 6, 2022

      @iritkatriel
      MemberAuthor

      New changeset 9925e70 by Irit Katriel in branch 'main':
      bpo-45292: [PEP-654] exception groups and except* documentation (GH-30158)
      9925e70

    16. transferred this issue fromon Apr 10, 2022
    17. added a commit that references this issue on Apr 14, 2022
    18. added a commit that references this issue on May 29, 2023
    19. added a commit that references this issue on May 29, 2023
    20. added a commit that references this issue on May 29, 2023
    21. added 2 commits that reference this issue on May 29, 2023
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    Labels

    3.11only security fixesdocsDocumentation in the Doc dirinterpreter-core(Objects, Python, Grammar, and Parser dirs)stdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions