Topics: to-string conversion, __repr__, __str__
Updated: 2020-10-04

To-string conversion

Today we are going to look at the two methods that control how an object is converted into a string object. When we just print an object, we won’t get a useful representation. For example, when trying to print the bromley instance:

bromley = CastleKilmereMember(name='Bromley Huckabee', birthyear=1959, sex='male')
print(bromley)

We get this output: <__main__.CastleKilmereMember object at 0x7f81853bfc50>. It contains the name of the class an the ID of the object (its memory address). This is not very useful when we want to know what the object is. But there is a simple solution to our problem!

We can control how objects of our classes are converted into string objects. Specifically, the to-string conversion is controlled by two methods: __repr__ and __str__ that serve different purposes:

The result of the __repr__ method should be as unambiguous as possible. It should function as a debugging aid for developers. Therefore, it should be explicit about what the object is. Furthermore, if possible, __repr__ should return a representation of the instance that can be used to recreate it.

The result of the __str__ method should be readable. Just think about how the object should look like for a user, not a developer.

By defining these special Python methods we can control how our objects will be represented when converting them into strings. It’s recommended to implement at least the __repr__ method for a class. Why __repr__? Because it will ensure a useful conversion of objects to strings. When Python looks for the __str__ method but __str__ hasn’t been implemented, it will fall back to using __repr__. So as long as __repr__ is defined we will get a helpful representation of our object.

Let’s add a __repr__ method to our CastleKilmereMember class!

class CastleKilmereMember:
    def __init__(self, name: str, birthyear: int, sex: str):
        ...

    def __repr__(self) -> str:
        return (f"{self.__class__.__name__}(name='{self.name}', "
                f"birthyear={self.birthyear}, sex='{self.sex}')")

Now, the string representation of Bromley is much more useful

print(bromley)
>>> CastleKilmereMember(name='Bromley Huckabee', birthyear=1959, sex='male')

Further, we can use the output of our __repr__ method to re-create the Bromley instance. This is done by first calling __repr__ and evaluating the result:

bromley == eval(repr(bromley))
>>> True

Since our subclasses inherit all methods from the parent class they will also contain an implementation for __repr__. But we want the output of __repr__ to be as unambiguous as possible. Therefore, we should add separate __repr__ methods to ensure that we use all information we have about the objects. For example, the __repr__ implementation of the Professor class will look as follows:

class Professor(CastleKilmereMember):
    """ Creates a Castle Kilmere professor """
    ...
    def __repr__(self) -> str:
        return (f"{self.__class__.__name__}(name='{self.name}', "
                f"birthyear={self.birthyear}, sex='{self.sex}', "
		f"subject='{self.subject}')")