OOP in Python, part 6: __str__() and __repr__()
MP 44: Providing different ways to represent instances of a class.
Note: This is the sixth post in a series about OOP in Python. The previous post discussed class methods. The next post covers the __new__()
method.
When people learn about OOP in Python, everyone learns about the __init__()
method. But there are two more “dunder” methods that are helpful to know about.
The __str__()
method is used to generate user-friendly representations of objects in printed output, while __repr__()
is used to generate representations that are targeted toward developers. There are default representations for each of these use cases, but Python makes it easy to override each of these methods when you need to.
The __str__()
method
At some point in your work with Python, you might have created an object from a class and then printed the object directly. The output probably seemed a little cryptic. Let’s do that now, and see what happens.
We’ll stick with Bonsai trees again in this post:
class BonsaiTree:
def __init__(self, name, description=""):
self.name = name
self.description = description
def describe_tree(self):
msg = f"{self.name}: {self.description}"
print(msg)
tree = BonsaiTree("Winged Elm")
print(tree)
Here, instead of calling a method like describe_tree()
, we’re just printing the instance tree
. Here’s the output:
<__main__.BonsaiTree object at 0x102e58c10>
If you don’t tell Python how to represent an object as a string, it just prints a message containing the following information:
The name of the file that’s being run;1
The name of the class that was used to create the instance;
The memory location where the object is stored.
This is useful information in debugging work, but this kind of output isn’t very user-friendly. The __str__()
method allows you to specify what should be printed when someone prints an object directly.
Here’s a __str__()
method for BonsaiTree:
class BonsaiTree:
def __init__(self, name, description=""):
...
def describe_tree(self):
...
def __str__(self):
return self.name
tree = BonsaiTree("Red Maple")
print(tree)
Now the output is much friendlier:
Red Maple
The __str__()
method is helpful for more than just printing on the command line, however. Many “dashboards” look for __str__()
methods when displaying data associated with instances of a class. For example in Django’s admin interface, data is represented using a model’s __str__()
method; that’s why you see a __str__()
method defined in most model classes.
Here’s the Question
model from the official Polls tutorial:
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date_published')
def __str__(self):
return self.question_text
If you’ve defined this method correctly, you’ll see your actual question text in the admin interface. If you forget to include this method, or misspell __str__()
, Django defaults to the generic representation of an instance of the model class. Instead of seeing something like What’s your favorite text editor?, you’ll see the name of the class and the instance’s primary key: Question object (1).2
The __repr__()
method
Before explaining what __repr__()
does, let’s use the class BonsaiTree
in a terminal session. If you open a terminal session at a directory containing a .py file, you can import classes and functions from that .py file:
$ ls
bonsai.py
$ python
>>> from bonsai import BonsaiTree
>>> tree = BonsaiTree("Red Maple")
>>> print(tree)
Red Maple
>>>
Here we import BonsaiTree
and make an instance called tree
. We then print that object directly; because we defined a __str__()
method earlier, we see the name of the tree.
Watch what happens if we just enter tree
in the terminal session, without wrapping it in a print()
call:
>>> tree
<bonsai.BonsaiTree object at 0x100d40610>
In a terminal session, Python’s default representation of an instance is a string containing the module name, the class name, the word “object”, and the memory address of the object.
If you want a custom string to be printed in environments like terminal sessions, you can write a custom __repr__()
method. Before writing your own __repr__()
method, you should be aware of what the official documentation has to say about this method:
If at all possible, this should look like a valid Python expression that could be used to recreate an object with the same value (given an appropriate environment). If this is not possible, a string of the form
<...some useful description...>
should be returned. The return value must be a string object.This is typically used for debugging, so it is important that the representation is information-rich and unambiguous.
A common alternative to the default representation is one that lets you recreate an equivalent instance from the class. Let’s add this kind of __repr__()
method to BonsaiTree
:
class BonsaiTree:
def __init__(self, name, description=""):
...
def describe_tree(self):
...
def __str__(self):
return self.name
def __repr__(self):
if self.description:
return (f"BonsaiTree(name={self.name}, "
f"description={self.description})")
else:
return(f"BonsaiTree(name={self.name})")
Rather than explaining this method, let’s look at what it does.
For this code to take effect, we need to start a new terminal session. Here’s the same commands we used earlier, in a new terminal session:
>>> from bonsai import BonsaiTree
>>> tree = BonsaiTree("Red Maple")
>>> tree
BonsaiTree(name='Red Maple')
This time when Python generates output after tree
is entered in the terminal session, it runs our custom __repr__()
method. Instead of a memory address, we get a Python snippet that we can use to generate an instance of BonsaiTree
that will be equivalent to the current tree
object.
In this example, the output is generated by the else
block of the __repr__()
method. If a description is provided, that will be included in the output:
>>> tree = BonsaiTree("Dwarf White Pine")
>>> tree.description = "Left-leaning trunk, long needles."
>>> tree
BonsaiTree(name='Dwarf White Pine',
description='Left-leaning trunk, long needles.')
Python calls __repr__()
when it generates debugging output as well. When looking at debugging output, it can be helpful to have exactly the information you need to recreate an object involved in the issue you’re working on.
Conclusions
Python has always had the goal of doing as much work as possible for you under the hood, while giving you the ability to override its default behavior whenever you need to. The __str__()
and __repr__()
methods are perfect examples of this.
When deciding whether to include either or both of these methods in a class, keep in mind that __str__()
is typically used to generate output for end users, while __repr__()
is used to generate output that only developers should see.
In the next post we’ll look at few more special methods you can override when needed, and then we’ll move on to inheritance.
Resources
You can find the code files from this post in the mostly_python GitHub repository.
In this context, the “name” of the file isn’t necessarily the name as it appears on your filesystem. When you run a .py file directly, Python sets the special variable __name__
to '__main__'
. Otherwise the name of the file will be the module name. This is usually the filename, minus the .py extension.
In Django, models typically inherit from the base Model class in the django.db.models
module. Here’s the __str__()
method from that class:
def __str__(self):
return "%s object (%s)" % (self.__class__.__name__, self.pk)
If you’re not familiar with this kind of string formatting, it’s equivalent to the following f-string:
def __str__(self):
return f"{self.__class__.__name__} object ({self.pk})"
This generates a string with the name of the class, the word “object”, and the primary key of the current instance in parentheses. For an instance of the Question
class with the primary key of 23
, this would generate the following string:
Question object (23)