-2

So I have this class with the repr method implemented

import reprlib

class Test:
    def __init__(self, aList):
        self.my_list = [c for c in aList]

    def __repr__(self):
#         return " ".join((str(i) for i in self.my_list))
#         return reprlib.repr(self.my_list)
#         return repr(self.my_list)  # builtin repr method
        return str(self.my_list)

What are the differences between the various implementations of the repr method?

They all have the same output.

Code used on all implementations:

x = [2, 5, 11]
t = Test(x)
print(t)  # >> [2, 5, 11]
t  # >> output [2, 5, 11]

Edit: The first implementation (join method) will produce the items without brackets. Disregard that if you like. My focus is what are differences among the last three and which is the better implementation among all four implementation.

Edit: This is clearly not about the differences between the repr and str methods. It's about which implementation should I consistently adopt when implementing the repr method (or the str method for that matter).

MAA
  • 1,294
  • 3
  • 18
  • 34
  • Possible duplicate of [What is the purpose of \_\_str\_\_ and \_\_repr\_\_?](https://stackoverflow.com/questions/3691101/what-is-the-purpose-of-str-and-repr) – ndmeiri Jun 14 '18 at 19:21
  • 3
    _"They all have the same output."_ That's weird, they aren't all the same on my machine. The first one doesn't print any square brackets, for instance. – Kevin Jun 14 '18 at 19:22
  • 1
    How is this a duplicate of the posted question?????? It absolutely has nothing to do with the difference between str and repr. I'm asking about the different implementation of repr. Didn't even mention str or tag it. My question is about the code inside the repr method. – MAA Jun 14 '18 at 19:27
  • 2
    The difference between your last two options is exactly the difference between `__str__` and `__repr__` since *that is what you are calling*. The fact that you've decided to potentially put them in `__repr__` for your class is almost irrelevant. I'm guessing that your real question is actually about a design decision, but frankly using a list as an example is probably not the best since it is so simple ... and I would say rather than a duplicate it's actually just close-worthy as *primarily opinion-based*. – Ajean Jun 14 '18 at 19:51
  • 1
    @Ajean when only one small part of the question is a duplicate, it's not a duplicate – o11c Jun 14 '18 at 20:05
  • what do you mean by "better implementation among all four implementation"? be more specific – Azat Ibrakov Jun 14 '18 at 20:07
  • 1
    @o11c Did you actually read my comment? I specifically said it's not a duplicate, but I was responding to OP's comment that it is completely unrelated (which it's not) – Ajean Jun 14 '18 at 20:08

3 Answers3

1

First, what I would write:

def __repr__(self):
    return '%s(%r)' % (self.__class__.__qualname__, self.my_list)

This preserves the primary purpose of a repr: to tell both the class and the important parts of the value. You could replace __qualname__ with __name__, or add __module__, but I find this is the best balance.

Sometimes, for more complicated cases (mostly, whenever parts of the repr should only appear conditionally), I wrote something like:

def __repr__(self):
    bits = []
    bits.append(...)
    return ''.join(bits)

All of your answers are missing the class, so I won't mention it again below.


def __repr__(self):
    return " ".join((str(i) for i in self.my_list))

This is bad for a lot of reasons, although it can be fixed:

  • Missing brackets. To fix, use '[%s]' % ...
  • Missing commas. To fix, use ', '.join(...)
  • Pointless use of a generator comprehension. Generators are very slow compared to list comprehensions, so whenever you're consuming the whole value immediately, always use a list comprehension.
  • Calls str instead of repr. This will produce confusing output for things like ['1', '2', '3']
def __repr__(self):
    return reprlib.repr(self.my_list)

This will truncate the list after some number of elements. Honestly, I find this more annoying than anything else.

The only function I ever use from that module is the reprlib.recursive_repr decorator, and even that only in the rare case that I can't just call builtin repr that is already recursion-aware. Notably, list is, so you don't need it here.

def __repr__(self):
    return repr(self.my_list)

This is as correct as any of your answers get. May cause problems for exceptionally-long lists. However, since reprs need more information than this, it often ends up being worth using '%r' % ... or {!r}.format(...)

def __repr__(self):
    return str(self.my_list)

This happens to produce the same output for list, but is semantically incorrect.


As an aside, instead of:

self.my_list = [c for c in aList]

use:

self.my_list = list(aList)
o11c
  • 15,265
  • 4
  • 50
  • 75
  • What is semantically wrong with `str(self.my_list)`? One should not confuse `str` inside `__repr__`? – Evgeny Jun 14 '18 at 20:04
  • @EvgenyPogrebnyak It's about expressing intention - and about forming good habits. – o11c Jun 14 '18 at 20:09
  • 1
    I wish I had a advanced version of Google translate for some of your thoughts - it is very unclear. _Semantically incorrect_ is...? str looks bad inside `__repr__`? or the result of a call is bad? – Evgeny Jun 14 '18 at 20:18
  • 1
    @EvgenyPogrebnyak Anybody can write a program that produces a given output. It takes discipline to consistently write programs that produce correct output *and* can be modified when requirements change. And discipline is closely tied to habit, as well as consciously following rules even when they have no immediate effect. – o11c Jun 14 '18 at 21:33
  • the upvote on your comment is mine – Evgeny Jun 14 '18 at 21:37
  • Thank you for the thorough answer. I especially like the aside point at the end. I will try the first implementation you mentioned. It's interesting. The pointless use of the generator expression and the str function is also very good points. Thanks again. o11c – MAA Jun 15 '18 at 20:16
1

My undestanding of __repr__ is that in the best possibility it should allow replicating the instance (perhaps even with eval()), and second-best is giving precise information about how to do that.

My preference for a mature __repr__ would be:

class Spammer:
    def __init__(self, members, hidden=False):
        self._list = list(members)
        self._hidden = hidden

    def __repr__(self):
        return 'Spammer(members={}, hidden={})'.format(self._list, self._hidden)

assert repr(Spammer([1,2], True)) == 'Spammer(members=[1, 2], hidden=True)'

The drawback of this approach is that you have to change __repr__ each time you introduce changes to __init__. Also __repr__has a tendency to fail silently in IDE without any exceptions, so explicit testing of it is necessary.

Evgeny
  • 4,173
  • 2
  • 19
  • 39
  • 1
    Using `eval` *programmatically* on repr is a horrible idea. More it's about "let's copy-paste this into a REPL and tweak it", at which point the `<...>` convention starts to matter. – o11c Jun 14 '18 at 20:03
  • Also, the `attr` module or numerous ad-hoc module conventions eliminate the need for changing `__repr__` when `__init__` changes. – o11c Jun 14 '18 at 20:04
  • @o11c - Can you elaborate on _attr module or numerous ad-hoc module conventions_? I do not understand what it means. – Evgeny Jun 14 '18 at 20:07
0

It depends on what you plan on doing with the class's repr.

  • If you are planning on doing custom formatting or the my_list attribute can be any iterable (though remember to avoid losing the data to the repr) instead of just a list, you should do something like the str.join method.

  • If you want to consider size and trim for that (making a large list display like Test([x, y, z, ...]) or something), consider using reprlib since it has functionality for that.

  • If you don't have any specific requirements, stick to whichever is simplest (namely repr, str and sometimes str.join)

Edward Minnix
  • 2,889
  • 1
  • 13
  • 26