11

Implementing __repr__ for a class Foo with member variables x and y, is there a way to automatically populate the string? Example that does not work:

class Foo(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return "Foo({})".format(**self.__dict__)

>>> foo = Foo(42, 66)
>>> print(foo)
IndexError: tuple index out of range

And another:

from pprint import pprint
class Foo(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return "Foo({})".format(pprint(self.__dict__))

>>> foo = Foo(42, 66)
>>> print(foo)
{'x': 42, 'y': 66}
Foo(None)

Yes I could define the method as

    def __repr__(self):
        return "Foo({x={}, y={}})".format(self.x, self.x)

but this gets tedious when there are many member variables.

BoltzmannBrain
  • 5,082
  • 11
  • 46
  • 79

4 Answers4

28

I use this as a mixin when I want something like that:

class SimpleRepr(object):
    """A mixin implementing a simple __repr__."""
    def __repr__(self):
        return "<{klass} @{id:x} {attrs}>".format(
            klass=self.__class__.__name__,
            id=id(self) & 0xFFFFFF,
            attrs=" ".join("{}={!r}".format(k, v) for k, v in self.__dict__.items()),
            )

It gives the class name, the (shortened) id, and all of the attributes.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • Nice one! Real classy. – Bubble Bubble Bubble Gut Jun 16 '17 at 18:01
  • 1
    Why isn’t it the default `object.__repr__` implementation? – Géry Ogam Mar 31 '22 at 15:14
  • Note that one should probably use `type(self).__name__` instead of `self.__class__.__name__` to avoid returning virtual class names for proxy objects (cf. [this answer](https://stackoverflow.com/a/53757539/2326961)). – Géry Ogam Mar 31 '22 at 15:17
  • @Maggyero: If you're writing a proxy object, you probably shouldn't use this mixin for it at all. It'll give misleading results no matter which class name you pick. – user2357112 Mar 31 '22 at 19:35
  • 1
    (I'd recommend using the full ID instead of shortening it - people will make assumptions about what the number after that `@` sign means, and I think the shorter output isn't worth the confusion caused by shortening the ID.) – user2357112 Mar 31 '22 at 19:41
  • @Maggyero it's not the default it could lead to infinite recursion if there's circular references. See https://stackoverflow.com/a/2626364/885922 – xlm May 30 '22 at 05:24
8

I think you want something like this:

    def __repr__(self):
        return "Foo({!r})".format(self.__dict__)

This will add repr(self.__dict__) in the string, using !r in a format specifier tells format() to call the item's __repr__().

See the "Conversion field" here: https://docs.python.org/3/library/string.html#format-string-syntax


Based on Ned Batchelder's answer, you can replace the line above by

return "{}({!r})".format(self.__class__.__name__, self.__dict__)

for a more generic approach.

  • 1
    If `self` refers to itself, such an implementation will lead to an infinite loop such that you will have to filter `self.__dict__` first. – jlaurens Oct 22 '20 at 09:10
0

Nice example!

for pretty output better to place simple return "\n{!r}".format(self.__dict__) and in root full print return "Class name: '{}' \n{!r}".format(self.__class__.__name__, self.__dict__)

Vatslau
  • 11
  • 2
0

Generally __repr__ means that you can copy-paste the result and it can easily be used again for creating a new object. Therefore, the following may look a bit hacky but will do the job. Also, notice how we need to treat string type arguments differently from numeric types.

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

    def __repr__(self):
        repr_str = f"{self.__class__.__name__}"
        repr_str += '('
        
        for key, val in self.__dict__.items():
            val       = f"'{val}'" if isinstance(val, str) else val
            repr_str += f"{key}={val}, "
        
        return repr_str.strip(", ") + ')'


>>> foo = Foo(42, 66)
>>> print(foo)
Foo(x=42, y=66)

>>> foo2 = Foo("abc", "xyz")
>>> print(foo2)
Foo(x='abc', y='xyz')