Skip to content

IDLE: add support for BaseExceptionGroup #139551

Closed

Description

@Locked-chess-official

Feature or enhancement

Proposal:

I experimentally change the print_exception() in idlelib.run and it can now print the ExceptionGroup:

# original
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    raise ExceptionGroup("", [NameError()])
ExceptionGroup:  (1 sub-exception)
# now
  + Exception Group Traceback (most recent call last):
  |   File "<pyshell#1>", line 1, in <module>
  |     raise ExceptionGroup("", [NameError()])
  | ExceptionGroup:  (1 sub-exception)
  +-+---------------- 1 ----------------
    | NameError
    +------------------------------------

The code is it:

def print_exception():
    import linecache
    linecache.checkcache()
    flush_stdout()
    efile = sys.stderr
    typ, val, tb = excinfo = sys.exc_info()
    sys.last_type, sys.last_value, sys.last_traceback = excinfo
    sys.last_exc = val
    seen = set()

    def print_exc(typ, exc, tb, prefix=""):
        seen.add(id(exc))
        context = exc.__context__
        cause = exc.__cause__
        exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
                   "debugger_r.py", "bdb.py")
        if cause is not None and id(cause) not in seen:
            print_exc(type(cause), cause, cause.__traceback__, prefix)
            if prefix:
                print(f"{prefix}|\n{prefix}| The above exception was the direct cause "
                      f"of the following exception:\n{prefix}|", file=efile)
            else:
                print("\nThe above exception was the direct cause "
                      "of the following exception:\n", file=efile)
        elif (context is not None and
                not exc.__suppress_context__ and
                id(context) not in seen):
            print_exc(type(context), context, context.__traceback__, prefix)
            if prefix:
                print(f"{prefix}|\n{prefix}| During handling of the above exception, "
                      f"another exception occurred:\n{prefix}|", file=efile)
            else:
                print("\nDuring handling of the above exception, "
                      "another exception occurred:\n", file=efile)
        if isinstance(exc, BaseExceptionGroup):
            if not prefix:
                print("  + Exception Group Traceback (most recent call last):", file=efile)
            else:
                print(f"{prefix}| Exception Group Traceback (most recent call last):", file=efile)
            if tb:
                tbe = traceback.extract_tb(tb)
                cleanup_traceback(tbe, exclude)
                for line in traceback.format_list(tbe):
                    for subline in line.rstrip().splitlines():
                        if not prefix:
                            print(f"  | {subline}", file=efile)
                        else:
                            print(f"{prefix}| {subline}", file=efile)
            lines = get_message_lines(typ, exc, tb)
            for line in lines:
                if not prefix:
                    print(f"  | {line}", end="", file=efile)
                else:
                    print(f"{prefix}| {line}", end="", file=efile)
  
            for i, sub in enumerate(exc.exceptions, 1):
                if i == 1:
                    first_line_pre = "+-"
                else:
                    first_line_pre = "  "
                if not prefix:
                    print(f"  {first_line_pre}+---------------- {i} ----------------", file=efile)
                else:
                    print(f"{prefix}{first_line_pre}+---------------- {i} ----------------", file=efile)
                if id(sub) not in seen:
                    if not prefix:                            
                        print_exc(type(sub), sub, sub.__traceback__, "    ")
                    else:
                        print_exc(type(sub), sub, sub.__traceback__, prefix + "  ")
                    need_print_underline = not isinstance(sub, BaseExceptionGroup)
                else:  # avoid infinitely printing the exception
                    if not prefix:
                        print("f    | <exception {type(sub).__name__} has printed>")
                    else:
                        print(f"{prefix}  | <exception {type(sub).__name__} has printed>")
                    need_print_underline = True
                if need_print_underline:
                    if not prefix:
                        print("    +------------------------------------", file=efile)
                    else:
                        print(f"  {prefix}+------------------------------------", file=efile)
  
        else:
            if tb:
                if prefix:
                    print(f"{prefix}| Traceback (most recent call last):", file=efile)
                else:
                    print("Traceback (most recent call last):", file=efile)
                tbe = traceback.extract_tb(tb)
                cleanup_traceback(tbe, exclude)
                if prefix:
                    for line in traceback.format_list(tbe):
                        for subline in line.rstrip().splitlines():
                            print(f"{prefix}| {subline}", file=efile)
                else:
                    traceback.print_list(tbe, file=efile)
            lines = get_message_lines(typ, exc, tb)
            for line in lines:
                if prefix:
                    print(f"{prefix}| {line}", end="", file=efile)
                else:
                    print(line, end='', file=efile)
  
    print_exc(typ, val, tb)

It is very close to the original python's ExceptionGroup output. However, it may needed more change. For example, how to deal with the print("f | <exception {type(sub).__name__} has printed>")

Has this already been discussed elsewhere?

No response given

Links to previous discussion of this feature:

No response

Linked PRs

Activity

  1. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 4, 2025
  2. terryjreedy commented on Oct 4, 2025

    @terryjreedy
    Member

    Nice. When I pasted raise ExceptionGroup("", [NameError()]) into current 3.15.0a0, I got the same output except for the file name, which is intentionally different in IDLE. I want want to put the body ot the if isinstance(exc, BaseExceptionGroup): clause in a separate function. Will want some unit tests. Will say more later.

    @Locked-chess-official What code did you look at to write this? In traceback module? In the new REPL code?

  3. Locked-chess-official commented on Oct 4, 2025

    @Locked-chess-official
    ContributorAuthor

    In fact, the frame of the code is mainly from chatGPT, I just modify it based on the output. And it is different in some condition, for example: One BaseExceptionGroup has a exception in the attribute exceptions that its "__cause__" or "__context__" is another BaseExceptionGroup, it prints extraly one line.

    a = ExceptionGroup("", [Exception()])
    a.exceptions[0].__cause__ = ExceptionGroup("", [ValueError()])
    raise a
    # idle
      + Exception Group Traceback (most recent call last):
      |   File "<pyshell#2>", line 1, in <module>
      |     raise a
      | ExceptionGroup:  (1 sub-exception)
      +-+---------------- 1 ----------------
        | Exception Group Traceback (most recent call last):
        | ExceptionGroup:  (1 sub-exception)
        +-+---------------- 1 ----------------
          | ValueError
          +------------------------------------
        |
        | The above exception was the direct cause of the following exception:
        |
        | Exception
        +------------------------------------
    # repl
      + Exception Group Traceback (most recent call last):
      |   File "<python-input-0>", line 3, in <module>
      |     raise a
      | ExceptionGroup:  (1 sub-exception)
      +-+---------------- 1 ----------------
        | ExceptionGroup:  (1 sub-exception)
        +-+---------------- 1 ----------------
          | ValueError
          +------------------------------------
        |
        | The above exception was the direct cause of the following exception:
        |
        | Exception
    
  4. changed the title [-]Add the support for BaseExceptionGroup in idle[/-] [+]IDLE: add support for BaseExceptionGroup[/+] on Oct 5, 2025
  5. removed
    stdlibStandard Library Python modules in the Lib/ directory
    on Jan 15, 2026
  6. added a commit that references this issue on Apr 12, 2026
  7. gpshead commented on Apr 12, 2026

    @gpshead
    Member

    cleaned up and merged, thanks for the contribution!

  8. added a commit that references this issue on Apr 13, 2026
  9. added a commit that references this issue on Apr 25, 2026
  10. added 2 commits that reference this issue on Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions