Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 85 additions & 17 deletions Lib/idlelib/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,30 +250,98 @@ def print_exception():
sys.last_exc = val
seen = set()

def print_exc(typ, exc, tb):
def print_exc(typ, exc, tb, prefix=""):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is anyway a local function, I'd suggest having other helpers to reduce the complexity of the function.

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__)
print("\nThe above exception was the direct cause "
"of the following exception:\n", file=efile)
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__)
print("\nDuring handling of the above exception, "
"another exception occurred:\n", file=efile)
if tb:
tbe = traceback.extract_tb(tb)
print('Traceback (most recent call last):', file=efile)
exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
"debugger_r.py", "bdb.py")
cleanup_traceback(tbe, exclude)
traceback.print_list(tbe, file=efile)
lines = get_message_lines(typ, exc, tb)
for line in lines:
print(line, end='', file=efile)
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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For instance, here, I would suggest using a separate function, say print_exc_group() that you would define locally as well.

Copy link
Member

@picnixz picnixz Oct 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change that if isinstance(val, BaseExceptionGroup) branch becomes a function alone is difficult because it is a closure function.

Even with that, it's not really important. What is the issue with it being a local function? print_exc is already a local function. Just pass the arguments you need to that local function.

if tb:
if not prefix:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's swap the if to be consistent with the other ifs.

print(" + Exception Group Traceback (most recent call last):", file=efile)
else:
print(f"{prefix}| Exception Group Traceback (most recent call last):", file=efile)
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer that - is stored as a variable, say sep = '-' * N where N is the number of '-' you want to print. You would also be able to use "prefix2".

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in traceback.py use many "-" to print instead of sep = '-' * N: (for example now in 3.13.7)

                # format exception group
                is_toplevel = (_ctx.exception_group_depth == 0)
                if is_toplevel:
                    _ctx.exception_group_depth += 1

                if exc.stack:
                    yield from _ctx.emit(
                        'Exception Group Traceback (most recent call last):\n',
                        margin_char = '+' if is_toplevel else None)
                    yield from _ctx.emit(exc.stack.format(colorize=colorize))

                yield from _ctx.emit(exc.format_exception_only(colorize=colorize))
                num_excs = len(exc.exceptions)
                if num_excs <= self.max_group_width:
                    n = num_excs
                else:
                    n = self.max_group_width + 1
                _ctx.need_close = False
                for i in range(n):
                    last_exc = (i == n-1)
                    if last_exc:
                        # The closing frame may be added by a recursive call
                        _ctx.need_close = True

                    if self.max_group_width is not None:
                        truncated = (i >= self.max_group_width)
                    else:
                        truncated = False
                    title = f'{i+1}' if not truncated else '...'
                    yield (_ctx.indent() +
                           ('+-' if i==0 else '  ') +
                           f'+---------------- {title} ----------------\n')
                    _ctx.exception_group_depth += 1
                    if not truncated:
                        yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx, colorize=colorize)
                    else:
                        remaining = num_excs - self.max_group_width
                        plural = 's' if remaining > 1 else ''
                        yield from _ctx.emit(
                            f"and {remaining} more exception{plural}\n")

                    if last_exc and _ctx.need_close:
                        yield (_ctx.indent() +
                               "+------------------------------------\n")
                        _ctx.need_close = False
                    _ctx.exception_group_depth -= 1

                if is_toplevel:
                    assert _ctx.exception_group_depth == 1
                    _ctx.exception_group_depth = 0

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I would like you to actually split the lines as it's done, not have everything on a single line (as you can see each group containing - is on its own line).

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add the support with BaseExceptionGroup in IDLE
Loading