Search code examples
pythonpicklesignatureabcrepr

Is this abstract base class with a "better" __repr__() dangerous?


It bugs me that the default __repr__() for a class is so uninformative:

>>> class Opaque(object): pass
... 
>>> Opaque()
<__main__.Opaque object at 0x7f3ac50eba90>

... so I've been thinking about how to improve it. After a little consideration, I came up with this abstract base class which leverages the pickle protocol's __getnewargs__() method:

from abc import abstractmethod

class Repro(object):

    """Abstract base class for objects with informative ``repr()`` behaviour."""

    @abstractmethod
    def __getnewargs__(self):
        raise NotImplementedError

    def __repr__(self):
        signature = ", ".join(repr(arg) for arg in self.__getnewargs__())
        return "%s(%s)" % (self.__class__.__name__, signature)

Here's a trivial example of its usage:

class Transparent(Repro):

    """An example of a ``Repro`` subclass."""

    def __init__(self, *args):
        self.args = args

    def __getnewargs__(self):
        return self.args

... and the resulting repr() behaviour:

>>> Transparent("an absurd signature", [1, 2, 3], str)
Transparent('an absurd signature', [1, 2, 3], <type 'str'>)
>>> 

Now, I can see one reason Python doesn't do this by default straight away - requiring every class to define a __getnewargs__() method would be more burdensome than expecting (but not requiring) that it defines a __repr__() method.

What I'd like to know is: how dangerous and/or fragile is it? Off-hand, I can't think of anything that could go terribly wrong except that if a Repro instance contained itself, you'd get infinite recursion ... but that's solveable, at the cost of making the code above uglier.

What else have I missed?


Solution

  • One problem with this whole idea is that there can be some kinds of objects who's state is not fully dependent on the arguments given to its constructor. For a trivial case, consider a class with random state:

    import random
    
    def A(object):
        def __init__(self):
            self.state = random.random()
    

    There's no way for this class to correctly implement __getnewargs__, and so your implantation of __repr__ is also impossible. It may be that a class like the one above is not well designed. But pickle can handle it with no problems (I assume using the __reduce__ method inherited from object, but my pickle-fu is not enough to say so with certainty).

    This is why it is nice that __repr__ can be coded to do whatever you want. If you want the internal state to be visible, you can make your class's __repr__ do that. If the object should be opaque, you can do that too. For the class above, I'd probably implement __repr__ like this:

    def __repr__(self):
        return "<A object with state=%f>" % self.state