Blog

__repr__ and __str__ in Python

This comes third, before properties or inheritance, because a class you cannot read in a traceback costs you time every single day you have it.

The default is useless

class Order:
    def __init__(self, id_, total):
        self.id, self.total = id_, total

o = Order(17, 249.5)
print(o)
print([o, o])

It prints something like:

<__main__.Order object at 0x7daca41360f0>
[<__main__.Order object at 0x7daca41360f0>, <__main__.Order object at 0x7daca41360f0>]

The address will differ on your machine, and that is the whole problem: the only thing Python can tell you is where the object lives in memory, which is never what you wanted to know.

__repr__ fixes it everywhere at once

class Order2:
    def __init__(self, id_, total):
        self.id, self.total = id_, total
    def __repr__(self):
        return f"Order2(id={self.id!r}, total={self.total!r})"

o2 = Order2(17, 249.5)
print(o2)
print([o2, o2])

It prints:

Order2(id=17, total=249.5)
[Order2(id=17, total=249.5), Order2(id=17, total=249.5)]

Note the second line. Containers use repr on their contents, which is why a list of objects with no __repr__ is a wall of addresses.

The convention is to return something you could paste back into Python to rebuild the object. Order2(id=17, total=249.5) meets that. Use !r on the values inside it, or a string field will lose its quotes and stop being valid.

str is for the reader, repr is for you

class Order3:
    def __init__(self, id_, total):
        self.id, self.total = id_, total
    def __repr__(self):
        return f"Order3(id={self.id!r}, total={self.total!r})"
    def __str__(self):
        return f"Order #{self.id} — {self.total:,.2f}"

o3 = Order3(17, 249.5)
print(str(o3))
print(repr(o3))
print(f"{o3}")
print(f"{o3!r}")

It prints:

Order #17 — 249.50
Order3(id=17, total=249.5)
Order #17 — 249.50
Order3(id=17, total=249.5)

print() and f-strings use str. The REPL, containers, and !r use repr.

Define __repr__ first, __str__ only if you need it

The fallback goes one way only. Define __repr__ alone and str() uses it:

class Minimal:
    def __init__(self, v): self.v = v
    def __repr__(self): return f"Minimal({self.v!r})"

m = Minimal(1)
print(str(m), '|', repr(m))

It prints:

Minimal(1) | Minimal(1)

Define only __str__ and repr() still gives you the address. So __repr__ is the one that always earns its place; __str__ is worth adding when the object is shown to a person.

Where it actually pays off

Error messages and tracebacks:

def charge(order):
    raise ValueError(f"cannot charge {order!r}")

try:
    charge(o3)
except ValueError as err:
    print(err)
try:
    charge(o)
except ValueError as err:
    print(str(err)[:46] + '...')

It prints:

cannot charge Order3(id=17, total=249.5)
cannot charge <__main__.Order object at 0x7dac...

The first tells you which order failed. The second tells you an object failed and sends you back to reproduce it. Over a year that difference is hours.

What to remember

  • Always write __repr__. It is the cheapest debugging improvement available.

  • Make it look like the call that would rebuild the object, and use !r on the fields.

  • __str__ is for output a person reads. It is optional, and falls back to __repr__.

  • Containers and !r use repr; print and f-strings use str.

Dataclasses generate __repr__ for you, which is one of the better reasons to reach for one. Part 8 covers that choice.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.