Classes in Python has a number of built-in features that you can customize. These features are accessible through methods defined by Python itself. Often, these methods are surrounded by two underscores, or a double underscore. Thus such a method is sometimes referred to as a “dunder method”.

Two of these “dunder methods”, __str__ and __repr__ let you customize the string representations of an object. In this post, you’ll see how to override the __str__ and __repr__ methods and more about their use cases. But first, we’ll need a class.

class TodoItem:
  def __init__(self, task, priority=0, complete=False):
    self.task = task
    self.priority = priority
    self.complete = complete
    
  def set_complete(self):
    self.complete = True

Now let’s create an instance of the TodoItem class (an object) and print it to the console.

if __name__ == "__main__":
  my_next_task = TodoItem("My next task")
  print(my_next_task)

Here is the result on my machine:

<__main__.TodoItem object at 0x7f02a442d1d0>

Not very useful. This simply say that my_next_task is an object of type TodoItem in the __main__ module at this memory address. Again the memory address on your machine will be different.

To make this a little more useful, let’s override the __repr__ (pronounced “dunder repper”) method to return the values of the attribute of the object.

class TodoItem:
  # existing code
  def __repr__(self):
    is_complete = "not complete" if not self.complete else "complete"
    return f"<TodoItem task='{self.task}', priority={self.priority}, {is_complete}>"

Run the app again and you’ll see this:

<TodoItem task='My next task', priority=0, not complete>

This is indeed more useful. It appears when you display the value of an object with the print function, behind the scenes, Python calls the __repr__ method of the object and displays the return value.

But what does the __str__ method do? Let’s add one and find out.

class TodoItem:
  # existing code
  def __str__(self):
    is_complete = "not complete" if not self.complete else "complete"
    return f"TodoItem(task='{self.task}', priority={self.priority}, {is_complete})"

Now run the app again.

TodoItem(task='My next task', priority=0, not complete)

Now it looks like when using the print function, the default behavior is to call the __str__ method, and output the return value to the console. But if there is not __str__ method override, use the __repr__ method. And if there is no __repr__ method, use the default representation with the type and memory location.

So what is the intended use of the __repr__ method other than a fallback when there is no __str__ override? To answer that question, we’ll need to open an interactive prompt or REPL such as IPython. At the prompt, import the TodoItem class. Create a new instance. Then get the value, but don’t use the print function.

Now we can see the intended use of the __repr__ function. It’s the default for getting the string representation of an object in the REPL. However, you can still use the print function and get the __str__ representation as well:

The __str__ representation will also be used if you call the str initializer.

In this example, we used string representations that were just different enough to tell which function they came from. But in the real world, what data should each one display?

If you think about it, you’re not going to have that many end users inspecting your code through an interactive REPL. Therefore, this is a use case for more technical users who are going to be looking for more technical information. And that is what should be displayed by the __repr__ method. But end users are going to want to see a more friendly string representation and in that case the __str__ method should be used.

Summary

In this post you saw the difference between the __str__ and __repr__ methods. The __str__ method is called when getting the string representation of an object via the print function or str initializer. It is intended to be friendly and consumed by end users. Technical users will be using tools like an interactive REPL and expect technical details in the string representation. This is when Python defaults to the __repr__ method string representation. There is also a default Python string representation that displays the type of the object and it’s memory address.

By Douglas Starnes

Entrepreneur, 5x Microsoft MVP, AI/BI nerd, crypto investor, content creator, trained composer, challenging the status quo, proud American

Leave a Reply

Your email address will not be published. Required fields are marked *