python-devtools icon indicating copy to clipboard operation
python-devtools copied to clipboard

`inspect` / `vars` function

Open samuelcolvin opened this issue 2 years ago • 0 comments

(I thought this was already an issue, but I checked and it seems it only existed in my head)

I want a way to pretty-print pertinent attributes of an object, like vars(...) but more intelligent - in particular it should try to inspect __slots__ if __dict__ is not available.

This might be as simple as sugar around the following POC, but there are probably edge cases I haven't thought about.

I guess we should expose this via debug.inspect().

Rough demo code of what I mean
from __future__ import annotations
from devtools import debug

ignored_attributes = {
    '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
    '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
    '__init__', '__init_subclass__', '__le__', '__lt__', '__module__',
    '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
    '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '__slots__',
}


class DebugInspect:
    __slots__ = ('obj',)

    def __init__(self, obj: Any):
        self.obj = obj

    def __pretty__(self, fmt: Callable[[Any], Any], **kwargs: Any) -> Generator[Any, None, None]:
        yield self.obj.__class__.__name__ + '('
        yield 1
        # use __dict__ if possible to maintain order, also should be slightly faster
        obj_dict = getattr(self.obj, '__dict__', None)
        if obj_dict is not None:
            for name, value in obj_dict.items():
                yield name + '='
                yield fmt(value)
                yield ','
                yield 0
        else:
            for name in dir(self.obj):
                if name not in ignored_attributes:
                    yield name + '='
                    yield fmt(getattr(self.obj, name))
                    yield ','
                    yield 0
        yield -1
        # closing bracket to keep it looking a bit like python
        yield ')'


class Foo:
    def __init__(self):
        self.x = 1
        self.y = 2
        self._private = 3
        self.__custom_dunder__ = 4


class Bar:
    __slots__ = 'x', 'y', '_private', '__custom_dunder__'

    def __init__(self):
        self.x = 1
        self.y = 2
        self._private = 3
        self.__custom_dunder__ = 4


f = Foo()
debug(DebugInspect(f))

b = Bar()
debug(DebugInspect(b))

prints:

foobar.py:61 <module>
    DebugInspect(f): Foo(
        x=1,
        y=2,
        _private=3,
        __custom_dunder__=4,
    ) (DebugInspect)
foobar.py:64 <module>
    DebugInspect(b): Bar(
        __custom_dunder__=4,
        _private=3,
        x=1,
        y=2,
    ) (DebugInspect)

samuelcolvin avatar Nov 25 '22 10:11 samuelcolvin