Skip to main content

Getting started with classes in Python Part 9: `__str__` vs `__repr__`

james.derrick@ansys.com | 02.24.2025

In Part 8 we introduced the concept of dunder methods and mentioned how they are used "under the hood" to perform much of the day-to-day business logic Python does behind the scenes. In this Part we will delve a little into a simple example of how to leverage two in particular.

In all classes there are two simple dunder methods called __str__ which is called whenever a class is turned into a string (hence the str) and __repr__ which is called whenever a representation of the class is needed. This is typically when a visual representation of the class is needed (this may sound similar to __str__ but it is subtly not as will be explained shortly). Each just returns a string, usually based on what's in the class. What's the difference? Well it's actually quite simple. The repr is for when something is returned visually but not printed. For example, if you are doing python in an interactive interpreter and output an object you will see that object's repr, but if you print the object with the print() function you will see the object's str.

There are two rules that come out of this.

  1. The __str__ method should be a useful visual representation of the class instance
  2. The __repr__ method should show what the class is, and ideally how to create this instantiation yourself. Ideally, it should be evaluatable code.

For example, below is a class I used for Advent of Code 2024 Day 10. The code snippet actually contains two classes, Tile and Point. Dataclasses in Python actually provide a built-in str and repr function that renders exactly what the class is and what each of the properties are.

from dataclasses import dataclass


@dataclass
class Point:
    x: int
    y: int


@dataclass
class Tile:
    point: Point
    height: str


tile = Tile(Point(0, 0), '0')
print(tile)

Printing out the instance of tile produces this output.

Tile(point=Point(x=0, y=0), height='0')

This is a perfect repr because if you were to copy and paste that code into the program it would create the object it came from. But as a str method result? It's quite verbose. However in the challenge we're going to have a lot of these tiles and this is unnecessarily verbose for the output too. What if instead of one we had to print out 500?

We can create a str method instead that makes the output more compact and to do so, we only need to add two lines to our existing script.

from dataclasses import dataclass


@dataclass
class Point:
    x: int
    y: int


@dataclass
class Tile:
    point: Point
    height: str

    def __str__(self) -> str:
        return f'(x:{self.point.x}-y:{self.point.y}-h:{self.height})'


tile = Tile(Point(0, 0), '0')
print(tile)

Now if we print tile we see the following output, which is a lot shorter and keeps the same amount of information available.

(x:0-y:0-h:0)

This is a pretty simple trick but the benefits are significant and can make UX and debugging significantly easier. Next time, in Part 10, we're going to tackle a topic I've been putting off, because it's a tricky subject, and we're finally ready to talk about it: inheritance.

Take a Step Back?

If you haven't already read the previous articles in this series, you may want to take a step back to look at some of the information that has brought us to this point. For a complete look at the articles in this series, you can visit the overview page: Getting started with classes in Python

Or, if you want to look directly at a particular previous article, here are the links:

  • Part 1: Anatomy of a Class
  • Part 2: dataclasses
  • Part 3: Methods and properties
  • Part 4: Hashing & Mutability
  • Part 5: classmethod and staticmethod
  • Part 6: Public, Private and Super-Private Naming
  • Part 7: getters and setters
  • Part 8: dunder methods