Customizing the string representation of your objects

Topic Series: Dunder methods

Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
3 minute read Works on Python 3.7—3.10
Share
Python Morsels
Watch as video
03:28

Let's talk about how to customize the string representations of your Python objects.

The default string representation

We have a Point class here:

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

We've made a new instance of this class and we're referring to that instance with the variable p. If we type p from the Python REPL, we see that it's a point.Point object at some memory location:

>>> p = Point(1, 2)
>>> p
<point.Point object at 0x7f128f519ee0>

If we print it we see the same thing:

>>> print(p)
<point.Point object at 0x7f128f519ee0>

And if we convert to a string explicitly, we see the same thing:

>>> str(p)
'<point.Point object at 0x7f128f519ee0>'

Explicit string conversions use __str__

It would be nice to see something, besides the module name and the class name of this object. Maybe the data that's actually stored in this object. Let's create a __str__ method on this Point class (pronounced "dunder str", dunder meaning "double underscore"):

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __str__(self):
        return f"({self.x}, {self.y})"

This will customize what happens when we pass the Point object to the built-in str function:

>>> p = Point(1, 2)
>>> str(p)
'(1, 2)'
>>> p.__str__()
'(1, 2)'

The built-in str function actually calls the __str__ method on the object that we give it.

In fact, if we print something out or otherwise, implicitly convert something to a string, it does the same thing: it calls that __str__ method.

>>> print(p)
(1, 2)

Programmer-readable string conversions use __repr__

We're not done yet! Our Python object does not yet have a nice string representation in all cases.

If we type p from the Python REPL, we still see the point.Point object at some memory location:

>>> p
<point.Point object at 0x7f7e54f46be0>

The REPL actually doesn't use, the str built-in function, it uses the built-in repr function.

>>> repr(p)
'<point.Point object at 0x7f7e54f46be0>'

And the built-in repr function relies on the __repr__ method:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return f"Point({self.x}, {self.y})"
    def __str__(self):
        return f"({self.x}, {self.y})"

By making a __repr__ method, we've customized what happens when you call repr on our Point objects:

>>> p = Point(1, 2)
>>> repr(p)
'Point(1, 2)'

And by making a __str__ method, we've customized what happens when you call str:

>>> str(p)
'(1, 2)'

And so when we just type p or when we print(p), we'll get friendly string representations:

>>> p
Point(1, 2)
>>> print(p)
(1, 2)

So, we've customized the programmer-readable string representation for the object (which by convention looks like Python code) and a human-readable string representation for our object, which is what an end-user might expect to see.

You really only need __repr__

Almost every Python object only customizes one of these: __repr__, the programmer readable string representation.

If we just define a __repr__ method on our class:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return f"Point({self.x}, {self.y})"

It will customize what programmers see:

>>> p = Point(1, 2)
>>> p
Point(1, 2)

But it also customizes what happens when we print out our object:

>>> print(p)
Point(1, 2)

On when we convert it to a string explicitly:

>>> str(p)
'Point(1, 2)'

This happens because the default __str__ method on all Python objects delegates to the __repr__ method.

>>> p.__str__()
'Point(1, 2)'

Summary

When you're defining your own classes in Python, always make a __repr__ method.

If you want an additional human-readable string representation, you might wanna customize the __str__ method, but you *at least& need to customize the __repr__ method of your Python objects.

Topic Trail: Dunder methods

You can overload many operators, protocols, and bits of functionality on your Python objects by implementing dunder methods.

To track your progress on this Python Morsels topic trail, sign in or sign up.

0%
Write more Pythonic code

Need to fill-in gaps in your Python skills? I send regular emails designed to do just that.