0

Consider the following class:

class ClusterAssignment(object):
def __init__(self, id, cluster):
    self.id = long(id)
    self.cluster = long(cluster)

@property
def id(self):
    return self._java_model.id()

@property
def cluster(self):
    return self._java_model.cluster()

def __str__(self):
  return self._str()

def _str(self):
    return "(%d,%d)" %(self.id(), self.cluster())

def __repr__(self):
    return self._str()

The attempt to override str and repr is not correct: they are unused.

ClusterAssignment(22, 234234)
Out[100]: <__main__.ClusterAssignment at 0x104ab3090>

Now there is an existing SOF that nominally addresses this topic:

Overriding special methods on an instance

However it is not clear how to apply the solutions provided in the above case.

Specifically: how would the custom _str() be configured to be invoked when either str or repr are called?

UPDATE I made a typo with the str vs _str() method when simplifying this to present on SOF. The same result occurs with the corrected code - which is updated above.

Community
  • 1
  • 1
WestCoastProjects
  • 58,982
  • 91
  • 316
  • 560
  • 2
    You never defined _str or __str; does the provided code even run? Where is self._java anything declared or stored on the cluster? What actually is happening in your code, is this Python? – dwanderson Apr 06 '15 at 23:25
  • You define `__str__(self)` twice.. why? – Tim Apr 06 '15 at 23:31
  • You can't override a method in the same class it's defined, only in a subclass. Did you mean "overload"? In case you did: Python doesn't overload at all. If you define a method twice in one class, even if the return type and parameters are different, the second definition simply replaces the first.) – Kevin J. Chase Apr 07 '15 at 01:11
  • @TimCastelijns I updated the OP: had made a typo in my simplification of original code. But the result is the same. – WestCoastProjects Apr 07 '15 at 01:54

2 Answers2

3

I think you made a couple mistakes implementing the workaround in your own code, and what you actually want is more like this

def __str__(self):
    return self._str()

def __repr__(self):
    return self._str()

def _str(self):
    return "(%d,%d)" %(self.id(), self.cluster())

So that whenever __str__() or __repr__() are called, they return the result of _str()

Tim
  • 41,901
  • 18
  • 127
  • 145
1

I can think of four ways to produce "identical" __str__ and __repr__ methods... not counting awfulness like copy & paste.

(For these examples, I'm using a stripped-down ClusterAssignment class with a meaningless string representation and no other methods.)

You can use Tim Castelijns' method, with a single "private" _str method, called by both __str__ and __repr__:

class ClusterAssignment(object):

    def _str(self):
        return "[string]"

    def __str__(self):
        return self._str()

    def __repr__(self):
        return self._str()

This works, but requires repeating yourself in the bodies of the two functions.

To avoid the repetition, you can write one of __str__ or __repr__ normally, and then have the other method do nothing but call the "real" method. Which method you write "for real" depends on your intended output --- is it more "stringy" or more "repr-y"?

There are then two ways to "call the 'real' method": using the dunder-method (first example below), or using the public interface (second example). I suggest using the public interface, since that's what your users have to do.

class ClusterAssignment(object):

    def __str__(self):
        return "[string]"

    def __repr__(self):
        # Uses "private" dunder-method.
        return self.__str__()


class ClusterAssignment(object):

    def __str__(self):
        return "[string]"

    def __repr__(self):
        # Uses public interface.
        return str(self)

Finally, you can give the one "real" method an alias:

class ClusterAssignment(object):

    def __str__(self):
        return "[string]"

    __repr__ = __str__

No repetitions here, and no redundant method definition: __repr__ is __str__.

I think this is explicit (better than implicit) --- there is only one task performed, so there is only one method. Others might see this as the opposite --- there are now two names masquerading as two methods, when there is really only one. Also, their order in the source code now matters --- __repr__ must be defined after __str__.

One quirk of the aliasing technique: The string representations of the methods themselves are now identical too. The first three versions produce:

>>> ca = ClusterAssignment()
>>> print(ca.__str__)
<bound method ClusterAssignment.__str__ of [string]>
>>> print(ca.__repr__)
<bound method ClusterAssignment.__repr__ of [string]>

The aliased version makes it clear that there is only one method:

>>> ca = ClusterAssignment()
>>> print(ca.__str__)
<bound method ClusterAssignment.__str__ of [string]>
>>> print(ca.__repr__)
<bound method ClusterAssignment.__str__ of [string]>

...or it sends your co-workers hunting for the bugged-up line that's calling __str__ when it should be calling __repr__.

This kind of decision is what project style guides are for.

Kevin J. Chase
  • 3,856
  • 4
  • 21
  • 43