3

I'm made an AutoRepr class and class decorator...

class AutoRepr:
    def __repr__(self):
        def _fix(thing):
            if isinstance(thing, str):
                return f'"{thing}"'
            if isinstance(thing, Iterable):
                s = str(thing)
                if len(s) > 30:
                    return type(thing)
                else:
                    return s
            return thing

        props = []
        try:
            for attr in self.__slots__:
                if attr.startswith('_'):
                    continue
                try:
                    attr_val = getattr(self, attr)
                    if attr_val:
                        props.append(f'{attr}={_fix(attr_val)}')
                except AttributeError:
                    pass
        except AttributeError:
            props = [f'{k}={_fix(v)}'
                     for k, v in self.__dict__.items()
                     if not k.startswith('_')]
        return f'{type(self).__name__}({", ".join(props)})'


def auto_repr(override_all=False):
    def decorator(cls):
        repr_defined_in = cls.__repr__.__qualname__.split('.')[0]
        if not override_all and repr_defined_in == cls.__name__:
            # repr overriden in class. Don't mess with it
            return cls
        cls.__repr__ = AutoRepr.__repr__
        return cls

    return decorator

# Example 1
@auto_repr()
class MyClass:
    def __init__(self):
        self.strength = None
        self.weakness = 'cake'

# Example 2
class Another(AutoRepr):
    __slots__ = ('num', 'my_list')
    def __init__(self):
        self.num = 12
        self.my_list = [1, 2, 3]


f = MyClass()
print(f)

b = Another()
print(b)

# MyClass(strength=None, weakness="cake")
# Another(num=12, my_list=[1, 2, 3])

In the decorator, I need to check if the wrapped class __repr__ was overridden in the class or belongs to a parent class. If the __repr__ has been overriden by the class then I don't want auto_repr to do anything, however, if not then obviously want auto-repr to do it's thing. I managed to hack together a solution by comparing the string name of the repr methods bound class via __qualname__, but it feels cheap and clunky. Ideally I'd like to check identity properly if cls is repr_defined_in_cls.

All the SO questions I've seen only address getting the string name of the class, but not the class for comparison. Is there a better way to get the (originating) class which defined the method?

martineau
  • 119,623
  • 25
  • 170
  • 301
nicholishen
  • 2,602
  • 2
  • 9
  • 13

2 Answers2

2

Check the class __dict__ for a "__repr__" key:

if "__repr__" in vars(cls):
    # repr overriden in class. Don't mess with it
else:
    cls.__repr__ = AutoRepr.__repr__
Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
  • Clever, I like that a lot better than my solution... I still would like to know how to get the class of a method though. Any ideas? – nicholishen Dec 21 '18 at 19:42
  • [It's not simple](https://stackoverflow.com/questions/3589311/get-defining-class-of-unbound-method-object-in-python-3/25959545#25959545). Basically, you walk up the MRO checking each class to see if it defines that method. – Patrick Haugh Dec 21 '18 at 19:45
  • Ah, I believe I misunderstood the question. You only want to know if the `__repr__` is being overriden in the class iteself, not just in any class other than object. Well, I think you can takes bits and pieces of my solution. – juanpa.arrivillaga Dec 21 '18 at 19:48
2

Sure, walk the mro and make sure that the first __repr__ you hit isn't from object:

In [18]: class A:
    ...:     pass
    ...:
    ...: class B(A):
    ...:     def __repr__(self): return "a repr"
    ...:
    ...: class C(B):
    ...:     pass
    ...:
    ...:

In [19]: def is_non_default_repr(klass):
    ...:     for superclass in klass.mro():
    ...:         if '__repr__' in vars(superclass):
    ...:             return superclass is not object
    ...:     return False # should we ever get here? maybe raise error?
    ...:
    ...:

In [20]: is_non_default_repr(A)
Out[20]: False

In [21]: is_non_default_repr(B)
Out[21]: True

In [22]: is_non_default_repr(C)
Out[22]: True

This will fail if a metaclass uses __slots__, I believe, but that would be quite an aberration.

Edit:

re-reading your requirements, you could just do something like:

In [23]: def which_method(klass, methodname):
    ...:     for superclass in klass.mro():
    ...:         if methodname in vars(superclass):
    ...:             return superclass
    ...:     raise ValueError(f"{methodname} not a member")
    ...:
    ...:

In [24]: which_method(C, '__repr__') is not object
Out[24]: True

In [25]: which_method(C, '__repr__')
Out[25]: __main__.B

In [26]: which_method(C, '__repr__') is C
Out[26]: False

In [27]: which_method(B, '__repr__') is B
Out[27]: True

Depending on exactly the semantics you require. I think you get the gist.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172