__repr__ vs __str__
41 days ago · 61 views
I thought I understood __repr__ and __str__. I was wrong.
Every time I needed a string representation, I'd write __str__ first. It felt natural: str() converts things to strings, so obviously that's the one you implement.
Then I'd run into a situation where my object printed weirdly in a list or the debugger, and I'd get annoyed and add __repr__ as an afterthought.
This is backwards.
The Thing that was not clear
__repr__ is not the "technical" version of __str__.
It's the foundation. If you only implement one, implement __repr__.
Here's why: if __str__ is missing, Python falls back to __repr__. But the reverse isn't true. If you only implement __str__, your objects will look broken everywhere except inside print() statements.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(3, 4)
print(p) # (3, 4) - looks fine
print([p]) # [<__main__.Point object at 0x...>] - broken
That second line? That's what happens when you put your object in a container. Containers don't call __str__. They call __repr__. And if you didn't implement it, you get garbage.
The Actual Rule
__repr__ is for you, the developer. It should be unambiguous. Ideally, you could paste it into the REPL and recreate the object. Point(x=3, y=4) is a good __repr__.
__str__ is for people who don't care about the internal structure. It can be vague, pretty, formatted however you want. (3, 4) is fine for __str__.
Most of the time, you don't need __str__ at all. Just write a good __repr__ and call it done.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
Now it works everywhere. Debugger, REPL, lists, logs. One method.
What Changed
I stopped thinking of __repr__ as the "optional debugging thing" and started treating it as the default. If I want something prettier for end users later, I can add __str__. But __repr__ comes first now.
I still mess this up sometimes. Old habits. But at least now I know why my objects keep looking broken in the debugger.