Optionally include attribute values in the output of traceback.StackSummary.format

Python 3.5 introduced the traceback.StackSummary.extract constructor with the awesome capture_locals option so that format and the underlying format_frame_summary methods would include values of all local variables at each frame in the output, immensely useful for a DEBUG-level logging output as demonstrated here:

However, glaringly missing from the output are the attribute values of objects that don’t have a meaningful/detailed enough __repr__ defined. Something like b = <__main__.Foo object at 0x152cfce8dcd0> just isn’t very helpful.

Currently my workaround is to patch repr in a closure that specifies types that need better details:

import sys
import traceback
from unittest.mock import patch

def dump_stack(detailed_types=(), file=sys.stdout):
    def detailed_repr(obj, _repr=repr):
        def repr_attributes(obj, indent_level=2):
            if isinstance(obj, detailed_types):
                seen.add(id(obj))
                indent = '    ' * indent_level
                for name, value in sorted(vars(obj).items()):
                    yield f'{indent}.{name} = {value}'
                    if id(value) not in seen:
                        yield from repr_attributes(value, indent_level + 1)
        seen = set()
        return '\n'.join([_repr(obj), *repr_attributes(obj)])

    with patch('builtins.repr', detailed_repr):
        print(
            *traceback.StackSummary.extract(
                traceback.walk_stack(sys._getframe(1)), capture_locals=True
            ).format(), sep='\n', file=file
        )

so that:

class Foo:
    def __init__(self, x, y):
        self.bar = Bar(x, y)
        self.foo = self

class Bar:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def foo(c):
    d = c + 1
    dump_stack(detailed_types=(Foo, Bar))

a = 1
b = Foo(2, 3)
foo(a)

would output something like (note the output of attribute values of b):

  File "./prog.py", line 37, in foo
    c = 1
    d = 2

  File "./prog.py", line 41, in <module>
    Bar = <class '__main__.Bar'>
    Foo = <class '__main__.Foo'>
    __annotations__ = {}
    __builtins__ = <module 'builtins' (built-in)>
    __cached__ = None
    __doc__ = None
    __file__ = '/home/wT8L4N/./prog'
    __loader__ = <_frozen_importlib_external.SourcelessFileLoader object at 0x1546bffd5c10>
    __name__ = '__main__'
    __package__ = None
    __spec__ = None
    a = 1
    b = <__main__.Foo object at 0x1546bfba8cd0>
        .bar = <__main__.Bar object at 0x1546bfba8550>
            .x = 2
            .y = 3
        .foo = <__main__.Foo object at 0x1546bfba8cd0>
    dump_stack = <function dump_stack at 0x1546bfab7160>
    foo = <function foo at 0x1546bf9c5a60>
    patch = <function patch at 0x1546bf31dd30>
    sys = <module 'sys' (built-in)>
    traceback = <module 'traceback' from '/usr/lib/python3.9/traceback.py'>

Demo: uGGI44 - Online Python3 Interpreter & Debugging Tool - Ideone.com

But it surely would be helpful to almost everyone to have this capability built-in as an optional detailed_types argument to StackSummary.format, StackSummary.format_frame_summary and TracebackException.format, wouldn’t it?

1 Like