Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Useful default __repr__ for nested class instances

Tags:

python

class

I have an abstract base class representing an interface. Subclasses of this class store as properties other subclasses of this class.

For example:

class AbstractBase(object):
    pass

class Child(AbstractBase):
    def __init__(self, cls1, cls2):
        assert isinstance(cls1, AbstractBase)
        assert isinstance(cls2, AbstractBase) # just to show they're instances

        self.cls1 = cls1
        self.cls2 = cls2

The depth and layout of the hierarchy cannot be known in advance, but will not be recursive.

What can I put as __repr__ on AbstractBase that will allow me to see the proprties of each child class in a useful way?

My current approach is:

from pprint import pformat

class AbstractBase(object):
    def __repr__(self):
        return self.__class__.__name__ + '\n' \ 
                + pformat({k:v for k,v in self.__dict__.iteritems()
                           if not '__' in k})

For a base class (with no properties which are subclasses of AbstractBase, this outputs something readable, eg:

MyClass
{'var1': 1,
 'var2': 2}

However, for classes with AbstractBase subclasses it breaks, as it's hard to tell where a parent class starts and another ends (given that further levels of nesting aren't given further indenting by the __repr__ above).

I'd be happy with something like the below, imagining cls1 and cls2 had a single int property var:

Child
{'cls1': {
          'var': 1,
         },
 'cls2': {
          'var': 0,
         }
}

Sadly, I don't know how to achieve this (or if it's even possible). Any thoughts?

like image 993
sapi Avatar asked Nov 02 '22 23:11

sapi


2 Answers

I like what I got doing it this way:

class AbstractBase(object):
    def __repr__(self, indent=2):
        result = self.__class__.__name__ + '\n'
        for k,v in self.__dict__.iteritems():
            if k.startswith('__'):
                continue
            if isinstance(v, AbstractBase):
                vStr = v.__repr__(indent + 2)
            else:
                vStr = str(v)
            result += ' '*indent + k + ': ' + vStr
        result += '\n'
        return result
like image 196
John Zwinck Avatar answered Nov 15 '22 06:11

John Zwinck


This is a slightly modified version of what John Zwinck proposed.

It considers how to format sequences, and changes the format slightly. It's not perfect at the moment, though - I think in particular, it will break for dictionaries, as the iterable component will only print the key.

   def __repr__(self, indent=2):
        result = self.__class__.__name__ + '\n'
        items = self.__dict__.items()

        for i,(k,v) in enumerate(items):
            if '__' in k:   
                continue    
            if isinstance(v, AbstractBase):
                vStr = '\n' + ' '*(indent + 2) + v.__repr__(indent + 4)
            elif isinstance(v, collections.Iterable):
                s = str(v)
                bstart = s[0]
                bend = s[-1]

                newIndent = indent + 3
                vStr = '\n' + ' '*(newIndent - 1) + bstart
                for j,item in enumerate(v):
                    if isinstance(item, AbstractBase):
                        if j:
                            vStr += ' '*newIndent
                        vStr += item.__repr__(newIndent + 2)
                    else:    
                        vStr += repr(item)
                    vStr += ',\n'
                vStr += ' '*(newIndent - 1) + bend
            else:              
                vStr = str(v)  
            result += ' '*indent + k + ': ' + vStr

            if i != len(items) - 1:
                result += '\n'

        result = re.sub('\n+', '\n', result)
        return result  
like image 33
sapi Avatar answered Nov 15 '22 04:11

sapi